Athena — mahmoud-consultancy/api.md

InterimPlaza Recruitment Platform — API Reference

REST API for the InterimPlaza recruitment platform (product label under Mahmoud Consultancy B.V.; built by GloryLabs — see root CLAUDE.md → Corporate Structure).

This reference is generated from the Spring Boot controllers in mahmoud-consultancy/backend. It is the canonical workspace API doc; for system design see architecture.md and for local setup see setup.md.


Conventions

| Property | Value | |---|---| | Base URL (local) | http://localhost:8090 | | Base URL (prod) | https://app.interimplaza.nl (backend behind shared ingress) | | Context path | none — endpoints are served at the application root | | Content type | application/json unless noted (file upload/download differ) | | Auth scheme | JWT Bearer — Authorization: Bearer <accessToken> | | Session policy | Stateless (SessionCreationPolicy.STATELESS) | | Pagination | Spring Data Pageablepage, size, sort query params; responses are Page<T> envelopes (content, totalElements, totalPages, number, size) |

⚠️ Path inconsistency (known). The codebase mixes two path styles: some controllers are mounted at the root (/jobs, /applications, /articles, /job-alerts, /crawler) while newer admin/CV controllers use /api/... and /api/v1/... prefixes. Paths below are documented exactly as the controllers declare them. A future cleanup should normalise everything under /api/v1.

Authentication flow

  1. POST /api/auth/register or POST /api/auth/login → returns AuthResponse with accessToken + refreshToken.
  2. Send Authorization: Bearer <accessToken> on protected requests.
  3. When the access token expires, POST /api/auth/refresh with the refresh token.
  4. If MFA is enabled, login returns an MFA challenge; complete it via POST /api/auth/mfa/verify-login to obtain tokens.

Roles & access tiers

| Tier | Rule | Routes | |---|---|---| | Public | permitAll() | /api/auth/**, /auth/**, /jobs/**, /api/jobs/**, /api/public/**, /crawler/**, swagger/actuator-health | | Authenticated | valid JWT | /applications/**, /articles/**, /job-alerts/**, /api/auth/me, MFA management, per-user bookmarks | | Recruiter | ROLE_ADMIN or ROLE_RECRUITER | /api/v1/admin/applications/**, /api/v1/admin/cv-profiles/**, /api/v1/admin/statistics, /admin/articles/** | | Admin | ROLE_ADMIN | /api/v1/admin/jobs/**, /api/v1/admin/users/**, /api/v1/admin/sources/**, article delete |

Ownership checks (a user may only read/modify their own applications, alerts, CV profiles) are enforced in the service layer and surface as 404 Not Found on unauthorized access rather than 403.

Standard error responses

| Status | Meaning | |---|---| | 400 | Validation failure (Bean Validation) / malformed body | | 401 | Missing or invalid JWT | | 403 | Authenticated but lacks the required role | | 404 | Not found, or owned-by-another-user (ownership-guarded resources) | | 409 | Conflict (e.g. duplicate registration) | | 413 | Payload too large (CV upload > 5 MB) |


Auth — AuthController

Base path /api/auth · public (permitAll)

| Method | Path | Auth | Request | Response | |---|---|---|---|---| | POST | /api/auth/register | public | RegisterRequest | AuthResponse (tokens) | | POST | /api/auth/login | public | LoginRequest | AuthResponse (tokens, or MFA challenge) | | POST | /api/auth/refresh | public | RefreshRequest | AuthResponse | | POST | /api/auth/logout | bearer | — (uses Authorization header) | 200 | | GET | /api/auth/verify-email | public | query token | String | | POST | /api/auth/forgot-password | public | ForgotPasswordRequest | String | | POST | /api/auth/reset-password | public | ResetPasswordRequest | String | | GET | /api/auth/me | bearer | — | UserDto |

POST /api/auth/login — example

POST /api/auth/login
Content-Type: application/json

{ "email": "user@example.com", "password": "••••••••" }
{
  "accessToken": "eyJhbGciOi…",
  "refreshToken": "eyJhbGciOi…",
  "tokenType": "Bearer",
  "user": { "id": 1, "email": "user@example.com", "role": "CANDIDATE" }
}

MFA — MfaController

Base path /api/auth/mfa · authenticated (except verify-login)

| Method | Path | Auth | Request | Response | |---|---|---|---|---| | POST | /api/auth/mfa/setup | bearer | — | TotpSetupResponse (secret + QR) | | POST | /api/auth/mfa/enable | bearer | TotpVerifyRequest (code) | 200 | | POST | /api/auth/mfa/disable | bearer | TotpDisableRequest (password) | 200 | | GET | /api/auth/mfa/status | bearer | — | TotpStatusResponse | | POST | /api/auth/mfa/verify-login | public | MfaLoginRequest | AuthResponse (tokens) | | POST | /api/auth/mfa/backup-codes/regenerate | bearer | TotpVerifyRequest | List<String> (backup codes) |


Jobs — JobController

Base path /jobs · public browse (permitAll); per-user actions authenticated

| Method | Path | Auth | Request | Response | |---|---|---|---|---| | GET | /jobs | public | query page,size,sortBy,sortDirection | Page<JobDto> | | GET | /jobs/search | public | query query,page,size | Page<JobDto> | | POST | /jobs/filter | public | JobFilterDto (+ query page,size) | Page<JobDto> | | GET | /jobs/{id} | public | — | JobDto | | POST | /jobs | bearer (recruiter, service-enforced) | JobDto | JobDto (201) | | PUT | /jobs/{id} | bearer (recruiter) | JobDto | JobDto | | DELETE | /jobs/{id} | bearer (recruiter) | — | 204 | | GET | /jobs/filters | public | — | Map<String,Object> (available filter values) | | GET | /jobs/statistics | public | — | Map<String,Object> | | GET | /jobs/{id}/sources | public | — | List<JobSourceLinkDto> | | GET | /jobs/{id}/similar | public | query limit (5) | List<JobDto> | | POST | /jobs/{id}/bookmark | bearer | — | 200 | | POST | /jobs/{id}/unbookmark | bearer | — | 200 | | GET | /jobs/{id}/bookmark | bearer | — | Boolean | | GET | /jobs/bookmarks | bearer | Pageable | Page<JobDto> |


Applications — ApplicationController

Base path /applications · authenticated

| Method | Path | Auth | Request | Response | |---|---|---|---|---| | POST | /applications | bearer | ApplicationDto | ApplicationDto (201) | | GET | /applications/my-applications | bearer | — | List<ApplicationDto> | | GET | /applications/check/{jobId} | bearer | — | ApplicationCheckDto | | GET | /applications/job/{jobId} | bearer (recruiter) | — | List<ApplicationDto> | | PUT | /applications/{id}/status | bearer | query status | ApplicationDto | | GET | /applications/{id}/history | bearer | — | List<ApplicationStatusHistoryDto> | | DELETE | /applications/{id} | bearer (candidate) | — | ApplicationDto (withdrawn) | | GET | /applications/statistics | bearer (recruiter) | — | Map<String,Object> | | GET | /applications | bearer | query status | List<ApplicationDto> |

CV upload — CvUploadController

Base path /api/applications · authenticated

| Method | Path | Auth | Request | Response | |---|---|---|---|---| | POST | /api/applications/upload-cv | bearer | multipart/form-data field file (PDF, ≤ 5 MB) | Map {fileName, filePath} |


Articles — ArticleController

Base path /articles · authenticated

| Method | Path | Request | Response | |---|---|---|---| | GET | /articles | Pageable (sort publishedAt) | Page<ArticleDto> | | GET | /articles/{slug} | — (increments view count) | ArticleDto | | GET | /articles/category/{category} | Pageable | Page<ArticleDto> | | GET | /articles/search | query q | Page<ArticleDto> | | GET | /articles/{id}/related | query limit (5) | List<ArticleDto> | | GET | /articles/popular | query limit (10) | List<ArticleDto> | | GET | /articles/recent | query limit (10) | List<ArticleDto> | | POST | /articles/{id}/rate | ArticleRatingDto | ArticleRatingDto | | GET | /articles/{id}/ratings | Pageable | Page<ArticleRatingDto> | | POST | /articles/{id}/bookmark | — | 200 | | POST | /articles/{id}/unbookmark | — | 200 | | GET | /articles/bookmarks | Pageable | Page<ArticleDto> |


Job alerts — JobAlertController

Base path /job-alerts · authenticated

| Method | Path | Request | Response | |---|---|---|---| | POST | /job-alerts | JobAlertDto | JobAlertDto (201) | | GET | /job-alerts | — | List<JobAlertDto> | | GET | /job-alerts/{id} | — | JobAlertDto | | PUT | /job-alerts/{id} | JobAlertDto | JobAlertDto | | DELETE | /job-alerts/{id} | — | 204 | | GET | /job-alerts/{id}/matches | query page (0), size (20) | Page<JobDto> |


CV profiles — CVProfileController (cv-service)

Base path /api/v1/cv-profiles · authenticated (JWT bearer)

| Method | Path | Request | Response | |---|---|---|---| | GET | /api/v1/cv-profiles | — | List<CVProfileDTO> | | GET | /api/v1/cv-profiles/{profileId} | — | CVProfileDTO | | GET | /api/v1/cv-profiles/{profileId}/full | — | CVProfileDTO (with relations) | | GET | /api/v1/cv-profiles/default | — | CVProfileDTO | | POST | /api/v1/cv-profiles | CVProfileDTO | CVProfileDTO (201) | | PUT | /api/v1/cv-profiles/{profileId} | CVProfileDTO | CVProfileDTO | | DELETE | /api/v1/cv-profiles/{profileId} | — | 204 | | POST | /api/v1/cv-profiles/{profileId}/duplicate | query newName | CVProfileDTO (201) | | PUT | /api/v1/cv-profiles/{profileId}/set-default | — | Map {message} | | GET | /api/v1/cv-profiles/{profileId}/generate-pdf | query download (true) | byte[] (application/pdf) |


Crawler — CrawlerController

Base path /crawler · public (permitAll) — used by the scraping subsystem

| Method | Path | Request | Response | |---|---|---|---| | POST | /crawler/crawl | CrawlRequest {url, crawlType?, maxPages?, initiatedBy} | Map {crawlJobId, status, message} | | GET | /crawler/status/{id} | — | CrawlJob | | GET | /crawler/history | query page,size | Page<CrawlJob> | | POST | /crawler/trigger-scraping | — | Map {success, message} | | POST | /crawler/trigger-flextender | — | Map {success, message} | | GET | /crawler/scraping-status | — | ScrapingStatus | | POST | /crawler/scrape | ScrapeRequest {url, createJob, company?, location?} | Map {success, title, content, url} |

The /crawler/** routes are permitAll at the application layer. In production they should be network-restricted (internal/scheduler use only); do not expose them publicly without an upstream guard.


Admin & recruiter API

All routes below require a JWT with the indicated role.

Dashboard statistics — AdminController

Base path /api/v1/admin · ROLE_ADMIN or ROLE_RECRUITER

| Method | Path | Request | Response | |---|---|---|---| | GET | /api/v1/admin/statistics | — | Map (totalUsers, newUsersThisMonth, totalJobs, activeJobs, totalApplications, pendingApplications, totalCVProfiles) | | GET | /api/v1/admin/statistics/applications-by-status | — | Map<String,Long> | | GET | /api/v1/admin/statistics/monthly-applications | query months (6) | List<Map> | | GET | /api/v1/admin/statistics/top-jobs | query limit (5) | List<Map> | | GET | /api/v1/admin/system/health | — | Map (status, timestamp, database, emailService) |

Application management — AdminApplicationController

Base path /api/v1/admin/applications · ROLE_ADMIN or ROLE_RECRUITER

| Method | Path | Request | Response | |---|---|---|---| | GET | /api/v1/admin/applications | Pageable | Page<ApplicationDto> | | GET | /api/v1/admin/applications/by-status/{status} | Pageable | Page<ApplicationDto> | | PUT | /api/v1/admin/applications/{id}/status | Map {status, message?, notes?} | ApplicationDto (sends email) | | PUT | /api/v1/admin/applications/{id}/notes | Map {notes} | ApplicationDto | | GET | /api/v1/admin/applications/by-job/{jobId} | Pageable | Page<ApplicationDto> | | GET | /api/v1/admin/applications/{id}/cv | — | Resource (inline PDF) | | GET | /api/v1/admin/applications/by-user/{userId} | Pageable | Page<ApplicationDto> | | DELETE | /api/v1/admin/applications/{id} | — | 204 | | PUT | /api/v1/admin/applications/bulk-update | Map {ids[], status, message?} | Map<String,Object> |

Job management — AdminJobController

Base path /api/v1/admin/jobs · ROLE_ADMIN

| Method | Path | Request | Response | |---|---|---|---| | GET | /api/v1/admin/jobs | query page,size,search?,active? | Page<JobDto> | | POST | /api/v1/admin/jobs | JobDto | JobDto (201) | | PUT | /api/v1/admin/jobs/{id} | JobDto | JobDto | | PUT | /api/v1/admin/jobs/{id}/toggle | — | JobDto (flips active) | | DELETE | /api/v1/admin/jobs/{id} | — | 204 |

Article management — AdminArticleController

Base path /admin/articles · ROLE_ADMIN or ROLE_RECRUITER (delete: ROLE_ADMIN)

| Method | Path | Request | Response | |---|---|---|---| | GET | /admin/articles | Pageable | Page<ArticleDto> (incl. unpublished) | | GET | /admin/articles/{id} | — | ArticleDto | | POST | /admin/articles | CreateArticleRequest | ArticleDto (201) | | PUT | /admin/articles/{id} | UpdateArticleRequest | ArticleDto | | DELETE | /admin/articles/{id} | — (admin only) | 204 | | POST | /admin/articles/{id}/publish | — | ArticleDto | | POST | /admin/articles/{id}/unpublish | — | ArticleDto |

Job source management — AdminJobSourceController

Base path /api/v1/admin/sources · ROLE_ADMIN

| Method | Path | Request | Response | |---|---|---|---| | GET | /api/v1/admin/sources | Pageable | Page<JobSourceDto> | | GET | /api/v1/admin/sources/type/{sourceType} | Pageable | Page<JobSourceDto> | | GET | /api/v1/admin/sources/search | query query | Page<JobSourceDto> | | GET | /api/v1/admin/sources/{id} | — | JobSourceDto | | POST | /api/v1/admin/sources | JobSourceDto | JobSourceDto (201) | | PUT | /api/v1/admin/sources/{id} | JobSourceDto | JobSourceDto | | DELETE | /api/v1/admin/sources/{id} | — | 204 | | PUT | /api/v1/admin/sources/{id}/toggle | — | JobSourceDto | | POST | /api/v1/admin/sources/{id}/scrape | — | ScrapeResult | | POST | /api/v1/admin/sources/scrape-all | — | String |

CV profile management — AdminCVProfileController

Base path /api/v1/admin/cv-profiles · ROLE_ADMIN or ROLE_RECRUITER

| Method | Path | Request | Response | |---|---|---|---| | GET | /api/v1/admin/cv-profiles | Pageable | Page<CVProfileDTO> | | GET | /api/v1/admin/cv-profiles/by-user/{userId} | — | List<CVProfileDTO> | | GET | /api/v1/admin/cv-profiles/{id} | — | CVProfileDTO | | GET | /api/v1/admin/cv-profiles/{id}/full | — | CVProfile (entity, nested) | | GET | /api/v1/admin/cv-profiles/{id}/download-pdf | — | byte[] (PDF attachment) | | DELETE | /api/v1/admin/cv-profiles/{id} | — | 204 | | GET | /api/v1/admin/cv-profiles/recent | query days (7) | Page<CVProfileDTO> | | GET | /api/v1/admin/cv-profiles/search | query query | Page<CVProfileDTO> |

User management — AdminUserController

Base path /api/v1/admin/users · ROLE_ADMIN

| Method | Path | Request | Response | |---|---|---|---| | GET | /api/v1/admin/users | Pageable | Page<UserDto> | | GET | /api/v1/admin/users/search | query query | Page<UserDto> | | GET | /api/v1/admin/users/{id} | — | UserDto | | PUT | /api/v1/admin/users/{id}/role | query role | UserDto | | PUT | /api/v1/admin/users/{id}/status | query enabled (boolean) | UserDto | | DELETE | /api/v1/admin/users/{id} | — | 204 (soft-delete) | | GET | /api/v1/admin/users/recent | query days (7) | List<UserDto> |


Notes & maintenance

  • This document is hand-derived from the controllers under mahmoud-consultancy/backend/src/main/java/nl/glorylabs/controller/ and the cv-service module. When endpoints change, regenerate this table or, better, rely on the live OpenAPI spec.
  • Live spec: the backend serves OpenAPI at /v3/api-docs and Swagger UI at /swagger-ui/** (both public). Use those for the always-current contract, including full DTO field shapes.
  • Recommended follow-up: normalise all routes under a single /api/v1 prefix to remove the root-vs-prefixed path inconsistency noted above.

Reacties

Nog geen reacties