Skip to content

API Contracts

  • Base URL: https://foot-factory-api.dotevolve.net/api/v1

Authentication

  • Method: JWT Bearer tokens
  • Header: Authorization: Bearer <token>
  • Session persistence: httpOnly refresh cookie (GET /auth/refresh)
  • Tenancy: The JWT payload contains tenantId. The tenantMiddleware automatically scopes all Mongoose queries to { tenantId: req.tenantId }, preventing cross-tenant data access.

Standard Response Format

Success:

{ "status": "success", "data": { ... } }

Error:

{ "status": "error", "message": "Human readable description" }

Multi-Tenancy Model

Every resource in the system (workers, articles, jobs, ledger entries, etc.) belongs to exactly one tenant. Tenant isolation is enforced at the middleware layer — not in individual route handlers.

How it works

  1. The client sends a JWT in the Authorization: Bearer <token> header.
  2. tenantMiddleware decodes the JWT and extracts tenantId.
  3. All Mongoose queries in that request are automatically scoped to { tenantId: req.tenantId }.
  4. Super-admin routes bypass tenant scoping and can access all tenants.

Tenant ID in the JWT

{
  "id": "user-object-id",
  "tenantId": "tenant-object-id",
  "role": "admin",
  "iat": 1700000000,
  "exp": 1700086400
}

Cross-tenant data access is structurally impossible for non-super-admin users — the middleware injects tenantId before any route handler runs.


Auth Endpoints (/api/v1/auth)

Method Path Auth Role Description
POST /auth/login No Login with username + password. Returns JWT + sets refresh cookie.
GET /auth/refresh Cookie Refresh JWT using httpOnly cookie. Returns new token + user.
GET /auth/logout No Clears the refresh cookie.
POST /auth/signup Yes admin Create a new user within the caller's tenant.
POST /auth/forgotPassword No Sends password reset email.
PATCH /auth/resetPassword/:token No Resets password using token from email.

Login Request

{ "username": "admin@example.com", "password": "secret" }

Login / Refresh Response

{
  "status": "success",
  "token": "<jwt>",
  "data": {
    "user": { "_id": "...", "name": "...", "email": "...", "role": "admin" }
  }
}

Authentication Flow

sequenceDiagram
    participant Client as Admin/Tenant Client
    participant API as Core API
    participant DB as MongoDB

    Client->>API: POST /auth/login (username, password)
    API->>DB: Verify credentials
    DB-->>API: User matched
    API-->>Client: 200 OK + JWT (Body) + refreshToken (httpOnly Cookie)

    note over Client,API: Subsequent Requests
    Client->>API: GET /tenant (Header: Auth Bearer JWT)
    API-->>Client: 200 OK (Data)

    note over Client,API: Token Expiry Flow
    Client->>API: GET /tenant (Header: Auth Bearer expired JWT)
    API-->>Client: 401 Unauthorized
    Client->>API: GET /auth/refresh (Sends httpOnly Cookie implicitly)
    API->>DB: Verify refresh token
    DB-->>API: Valid session
    API-->>Client: 200 OK + New JWT (Body)

Step 1 — Acquire a token

POST /auth/login
{ "username": "admin@example.com", "password": "secret" }

Response sets an httpOnly refreshToken cookie and returns a short-lived JWT:

{
  "status": "success",
  "token": "<jwt>",
  "data": {
    "user": { "_id": "...", "name": "...", "email": "...", "role": "admin" }
  }
}

Step 2 — Use the token

Include the JWT in every subsequent request:

Authorization: Bearer <jwt>

Step 3 — Refresh the token

When the JWT expires (401 response), call:

GET /auth/refresh

The httpOnly cookie is sent automatically. Returns a new JWT. The admin client handles this automatically via an Axios interceptor.

Step 4 — Logout

GET /auth/logout

Clears the httpOnly refresh cookie. The client should also discard the JWT from memory.

Error Responses

Status Meaning
400 Missing or invalid fields
401 Invalid credentials or expired token
403 Role not permitted (e.g. non-super-admin login to admin panel)

Tenant Endpoints (/api/v1/tenant)

All routes require super-admin role.

Method Path Description
GET /tenant List all tenants
POST /tenant Provision a new tenant (atomic — creates tenant + admin user)
GET /tenant/stats Aggregate tenant counts grouped by status
GET /tenant/:id Get a single tenant
PATCH /tenant/:id Update tenant fields
PATCH /tenant/:id/status Suspend or reactivate a tenant { status: 'suspended' \| 'active' }
POST /tenant/:id/retry-domain Retry subdomain provisioning for a tenant with domainStatus: "pending"
DELETE /tenant/:id Delete a tenant

Provision Tenant Request

{
  "name": "Acme Corp",
  "slug": "acme-corp",
  "plan": "professional",
  "adminName": "John Admin",
  "adminEmail": "john@acme.com",
  "adminPassword": "securepass",
  "adminConfirmPassword": "securepass"
}

The plan value must match an active Plan slug in the database. The tenant's settings.maxUsers and settings.maxWorkers are automatically populated from the plan limits at provisioning time.

Provision Tenant Responses

201 Created — tenant created and subdomain fully provisioned:

{
  "status": "success",
  "data": {
    "tenant": {
      "_id": "...",
      "name": "Acme Corp",
      "slug": "acme-corp",
      "domain": "acme-corp.dotevolve.net",
      "domainStatus": "active"
    }
  }
}

207 Multi-Status — tenant created but subdomain provisioning partially or fully failed:

{
  "status": "partial",
  "data": {
    "tenant": {
      "_id": "...",
      "name": "Acme Corp",
      "slug": "acme-corp",
      "domain": "acme-corp.dotevolve.net",
      "domainStatus": "pending"
    }
  },
  "provisioningResult": {
    "subdomain": "acme-corp.dotevolve.net",
    "vercel": { "status": "failed", "error": "Unauthorized" },
    "cloudflare": { "status": "success" }
  }
}

A 207 means the tenant is fully created in the database — only the DNS/Vercel setup is pending. The super admin can retry via POST /tenant/:id/retry-domain.

Retry Domain Request

POST /tenant/:id/retry-domain — no request body required.

Response (200):

{
  "status": "success",
  "data": { "tenant": { "domainStatus": "active", ... } },
  "provisioningResult": {
    "subdomain": "acme-corp.dotevolve.net",
    "vercel": { "status": "success" },
    "cloudflare": { "status": "success" }
  }
}

Returns 400 if domainStatus is already "active". Returns 404 if tenant not found.

Error Responses

Status Meaning
207 Tenant created but subdomain provisioning partially failed
400 Validation error (missing required fields, invalid plan slug)
404 Tenant not found
409 Slug already in use
500 Unexpected server error

Tenant Config Endpoints (/api/v1/tenant-config)

Method Path Auth Role Description
POST /tenant-config Yes super-admin Upsert (create or update) a tenant's schema config
GET /tenant-config/:tenantId Yes any Fetch a tenant's schema config

Upsert Config Request

{
  "tenantId": "5f9c...",
  "jobSteps": [
    { "name": "Cutting", "order": 1, "isPaid": true },
    { "name": "Stitching", "order": 2, "isPaid": true },
    { "name": "Packing", "order": 3, "isPaid": false }
  ],
  "articleAttributes": [
    { "name": "color", "label": "Color", "type": "string", "isRequired": true },
    { "name": "size", "label": "Size", "type": "number", "isRequired": true }
  ],
  "sizeRange": { "min": 6, "max": 12 },
  "customLedgerTypes": ["advance", "bonus"],
  "pagination": { "defaultPageSize": 50, "maxPageSize": 200 }
}

Error Responses

Status Meaning
400 Invalid config shape
404 Tenant not found

Global Config Endpoints (/api/v1/global-config)

All routes require super-admin role. Manages platform-level configuration (not per-tenant).

Method Path Description
GET /global-config/cors List dynamic CORS origins
POST /global-config/cors Add a CORS origin { origin: string }
DELETE /global-config/cors/:origin Remove a CORS origin (URL-encoded)

Dynamic CORS origins are polled by the API every 30 seconds. New origins take effect within 60 seconds without a restart.


Plan Endpoints (/api/v1/plan)

Method Path Auth Role Description
GET /plan Yes any List all plans
GET /plan/:slug Yes any Get a single plan by slug
POST /plan Yes super-admin Create a new plan
PATCH /plan/:slug Yes super-admin Update a plan (slug is immutable)
DELETE /plan/:slug Yes super-admin Delete a plan (blocked if tenants are on it)

Plan Object

{
  "_id": "...",
  "name": "Professional",
  "slug": "professional",
  "description": "Mid-tier plan for growing factories",
  "limits": {
    "maxUsers": 10,
    "maxWorkers": 100,
    "maxJobSteps": 20,
    "maxArticleAttributes": 20
  },
  "rateLimits": {
    "maxRequests": 200,
    "windowMs": 900000
  },
  "isActive": true
}

Error Responses

Status Meaning
400 Validation error
404 Plan not found
409 Plan slug already exists, or plan is in use by tenants (on delete)

Worker Endpoints (/api/v1/worker)

All routes require admin or super-admin role. All queries are tenant-scoped.

Method Path Description
GET /worker List all workers
POST /worker/create Create a worker
GET /worker/:id Get a worker
PATCH /worker/:id Update a worker
DELETE /worker/:id Delete a worker
GET /worker/initDefaultWorker Seed the default "None" worker for a tenant

Worker skills are stored as a skills: [String] array on the Worker document. There are no hardcoded role flags (upperMan, bottomMan, etc.) — skill filtering is done by querying the skills array.

Error Responses

Status Meaning
400 Validation error
404 Worker not found

Article Endpoints (/api/v1/article)

All routes require admin or super-admin role. All queries are tenant-scoped.

Method Path Description
GET /article List all articles
POST /article/create Create an article
GET /article/getArticleList Lightweight list for dropdowns
POST /article/:id Get a single article
PATCH /article/:id Update an article
DELETE /article/:id Delete an article

Error Responses

Status Meaning
400 Validation error
404 Article not found

Job Endpoints (/api/v1/job)

All routes require admin or super-admin role. All queries are tenant-scoped.

Method Path Description
POST /job List all jobs (POST to support body filters)
POST /job/create Create a new job
GET /job/:id Get a single job
PATCH /job/:id Update job step status
DELETE /job/:id Delete a job

Error Responses

Status Meaning
400 Validation error
404 Job not found

Ledger Endpoints (/api/v1/ledger)

All routes require admin or super-admin role. All queries are tenant-scoped.

Method Path Description
POST /ledger/worker/:workerId Get ledger entries for a worker (date filters in body)
POST /ledger/summary/:workerId Get aggregated wage summary for a worker
POST /ledger/adjustment Create a manual wage adjustment entry

Error Responses

Status Meaning
400 Invalid date range or missing workerId
404 Worker not found

Report Endpoints (/api/v1/report)

All routes require admin or super-admin role. All queries are tenant-scoped.

Method Path Description
POST /report/getTaskReportByGroup Grouped production report
POST /report/getReportByArticleId/:id Report filtered by article

Transaction Endpoints (/api/v1/transaction)

All routes require admin or super-admin role. All queries are tenant-scoped.

Method Path Description
POST /transaction List all transactions
POST /transaction/create Create a transaction
POST /transaction/makePayment Process a payment
GET /transaction/:id Get a single transaction
PATCH /transaction/:id Update a transaction
DELETE /transaction/:id Delete a transaction

Error Responses

Status Meaning
400 Validation error
404 Transaction not found

Master Data Endpoints (/api/v1/master-data-values)

All routes require admin or super-admin. Tenant-scoped. Stores allowed values for each dynamic article attribute defined in TenantConfig.articleAttributes.

Method Path Description
GET /master-data-values/:tenantId Fetch all attribute value sets for a tenant
POST /master-data-values Upsert value list for a specific attribute

There are no separate endpoints per attribute type (no /color, /material, /sole, /box). All attribute values are managed through a single MasterDataValues collection keyed by attributeName.