# L'Oréal Studio Tracker — External API (v1) Server-to-server API for creating projects and deliverables from external systems (e.g. OMG, brief intake forms, integration scripts). Base URL: `https://optical-dev.oliver.solutions/loreal-prod-tracker/api/v1` ## Auth Send the shared API key on every request: ``` x-api-key: ``` The key matches `process.env.API_KEY` on the server. Requests with a missing or wrong key get `401 Unauthorized`. Optionally specify which organization the request applies to: ``` x-org-id: ``` If omitted, requests apply to the first organization in the database (useful in single-org deployments). ## Idempotency POST endpoints honour `Idempotency-Key`. A retried request with the same key + same payload returns the cached response without re-executing: ``` idempotency-key: 7f3c8e2a-...-deterministic-uuid ``` The same key with a *different* payload returns `409 Conflict`. Records expire after 24 hours. ## Endpoints ### `GET /api/v1/projects` List all projects visible to the caller. Returns the same shape as the in-app `/api/projects`. ### `POST /api/v1/projects` Create a project. If `pipelineTemplateId` is omitted, the org's default pipeline template is auto-applied. Body matches `createProjectSchema` (see `src/lib/validators/project.ts`). Required fields: - `projectCode` (string, unique per org) - `name` (string) Recommended fields: - `omgJobNumber` (string) — enables Box-folder matching downstream - `clientTeamId` (string) — drives team visibility - `requestorUserId` (string) — project owner - `priority` (`LOW` | `MEDIUM` | `HIGH` | `URGENT`) - `status` (`PIPELINE` | `ACTIVE` | …) - `startDate`, `dueDate` (ISO 8601) Returns `201` + the created project. ```bash curl -X POST \ -H "x-api-key: $API_KEY" \ -H "idempotency-key: $(uuidgen)" \ -H "content-type: application/json" \ -d '{ "projectCode": "API-001", "name": "Test integration project", "omgJobNumber": "12345", "priority": "MEDIUM" }' \ https://optical-dev.oliver.solutions/loreal-prod-tracker/api/v1/projects ``` ### `GET /api/v1/projects/{projectId}` Read-back. Same shape as in-app `/api/projects/{id}` — includes deliverables, stages, attachments, requestor user, client team. ### `POST /api/v1/projects/{projectId}/deliverables` Create a deliverable on the given project. Pipeline stages auto-applied from the project's template (`Deliverable.pipelineTemplate` cascades to `DeliverableStage` rows). Body matches `createDeliverableSchema`. Required fields: - `name` (string) Common fields: - `priority`, `dueDate`, `notes`, `externalId` (idempotency for upstream webhook callers — distinct from the `Idempotency-Key` header) Returns `201` + the created deliverable with its stage chain populated. ```bash curl -X POST \ -H "x-api-key: $API_KEY" \ -H "idempotency-key: $(uuidgen)" \ -H "content-type: application/json" \ -d '{ "name": "Hero banner", "priority": "HIGH", "dueDate": "2026-06-30T12:00:00Z" }' \ https://optical-dev.oliver.solutions/loreal-prod-tracker/api/v1/projects/$PROJECT_ID/deliverables ``` ## Error shapes ```json { "error": "Project code \"X\" already exists" } ``` All errors return JSON with an `error` string. Status codes: - `400` — validation error (zod issues joined into one message) - `401` — missing/wrong API key - `404` — project not visible (RBAC) or not found - `409` — Idempotency-Key reused with a different payload - `500` — server error (check server logs) ## What's NOT here - Update/delete endpoints — out of scope for v1. External callers create; in-app users update. - Deliverable read-back — `GET /api/v1/projects/{id}` includes deliverables in its response. A dedicated deliverable read endpoint can be added if external callers need it. - Bulk endpoints — call POST per project/deliverable. ## Internal notes - Auth path: `src/middleware.ts` short-circuits API-key requests before the session check. `src/lib/api-utils.ts` `getAuthSession()` resolves the key into a synthetic ADMIN session for the chosen org. - Idempotency: `src/lib/api/idempotency.ts` + `IdempotencyRecord` prisma model. 24-hour TTL — sweep via cron (see `scripts/`). - Default pipeline fallback: `src/lib/services/project-service.ts` `createProject()` — looks up the org's `PipelineTemplate.isDefault` if no `pipelineTemplateId` is supplied.