How clients authenticate, where the generated OpenAPI definition lives, and what the common requests look like.
Resources are annotated with Swagger annotations. After the application is started, the Swagger service definition JSON is served at http://localhost:8080/v3/api-docs, and Swagger UI is served at http://localhost:8080/swagger-ui.html.
Both live at the root of the server rather than under /api. Every endpoint the application itself defines is published under /api — PrefixedApiRequestHandler adds that prefix to the mappings in the com.abelium packages — but springdoc's own endpoints are not touched by it. http://localhost:8080/api/swagger-ui.html does not exist.
The reviewed API contract is stored in docs/api-contract/openapi.json. The
test suite exports the normalized runtime document and its provenance manifest
to target/api-contract/, then compares it to that baseline. Normal verification
never updates the reviewed file. For an intentionally reviewed API change, run
mvn -Dapi.contract.update=true -Dtest=OpenApiContractTest test, inspect the
baseline diff, and include the consumer compatibility review in the change.
Using Postman, you can create a collection from the Swagger definition.
- Select
Import > Link - Enter http://localhost:8080/v3/api-docs
- Click
Continue - Review configuration
- Click
Import - Change host variable
- Click on newly created collection
- Click
Variables - Edit row
baseUrlto containlocalhost:8080as current value
All requests are populated with sample requests, but the content is random. Fill in data to accommodate your test case.
API responses include an X-Request-ID header. Support can use this ID to find
the corresponding backend request-completion log, which records the HTTP
method, route template, final status, duration and, when available, the internal
authenticated user ID and role. The ID is diagnostic only, not a credential or
proof of authorization. No selected-company ID is inferred from client input.
Clients may supply exactly one canonical UUID (8-4-4-4-12 hexadecimal digits,
case-insensitive, any UUID version) in X-Request-ID; it is returned in
normalized lowercase form. If absent, malformed, repeated or comma-separated,
the server generates a new random v4 UUID instead. Rejected input is not logged.
An upstream gateway that generates IDs must use this format; nginx's usual
32-hex $request_id is not a canonical UUID and will be replaced.
Mail tasks and exchange-rate error callbacks retain the originating ID when
they are started by an API request; work started independently reports none.
This does not constitute distributed tracing or automatic propagation to every
thread or external service.
The backend does not log request or response bodies, cookies, tokens, passwords, raw query strings, client IPs, uploaded data, signed URLs or precise coordinates in its request-completion and reviewed error logs. Exception diagnostics omit messages and payloads, preserving exception types and bounded stack frames for unexpected errors. Controlled API errors record a category, HTTP status and, where available, an API status; not every 5xx has an exception or another diagnostic event. The request ID is included explicitly in the reviewed log messages; the default log pattern does not automatically add MDC to every log.
The former no-op error counter was removed; metrics are deferred to a separate observability task. The existing analytics feature that persists IP and geolocation data is separate and is not changed by this request logging.
Same-origin browser clients can read the response header without CORS exposure. This feature does not establish a cross-origin policy; a future restricted policy must expose the header explicitly. Proxy, container and external-service logs have their own privacy and retention settings. An error generated by a gateway before a request reaches the backend may not contain a backend request ID or have a corresponding completion log. Container logging is also outside the application's sanitization: for example, Tomcat may log an uncaught async task exception with its message and stack trace. Review container log settings and avoid putting secrets in exception messages. Some external-service requests include credentials in their URLs. Review third-party HTTP-client and gateway logging before enabling verbose DEBUG or wire logging; the application's sanitized diagnostics cannot control those external loggers.
Clients authenticate against POST /api/user/login endpoint and receive a JWT token in the Set-Cookie response header. The token is valid for 1 hour. See section Common requests below for a sample login request.
| Response header | Value |
|---|---|
| Set-Cookie | inatrace-accessToken=JWT; Path=/; Max-Age=3600; Expires=Thu, 24 Mar 2022 13:33:34 GMT; HttpOnly |
When accessing secured endpoints, the token has to be provided in the Cookie request header.
| Request header | Value |
|---|---|
| Cookie | inatrace-accessToken=JWT |
Login also sets an inatrace-refreshToken cookie after the access cookie. Each
login has a separate refresh session: another login to the same account does
not replace it. POST /api/user/refresh_authentication accepts that refresh
cookie and returns an access cookie; the refresh token remains reusable until
it expires or is revoked. The authenticated POST /api/user/logout revokes
the refresh token presented by that request and clears its cookies. A password
reset revokes the account's previous refresh tokens before issuing a new session
to an active account. Revocation is enforced when refreshing; access JWTs remain
valid until expiration unless the user's
status no longer permits access; logout/reset do not revoke an access JWT.
Token lifetimes are controlled by the deployment's authentication settings.
New JWTs include a token_use claim identifying access or refresh. Protected
endpoints accept only access JWTs with the access purpose; untyped legacy JWTs
and refresh JWTs cannot be used as access tokens. The refresh endpoint continues
to accept an existing untyped refresh token only while its stored session is
active and unexpired and its user is active. It then issues a typed access JWT.
Before deploying independent refresh sessions, inspect the existing
AuthenticationToken schema for a unique index on user_id; such a custom
constraint is incompatible with multiple sessions and must be addressed in a
reviewed migration. The checked-in mapping has always used ManyToOne and
does not declare that uniqueness. The migration for the expiration index
preserves existing token rows. Deploy all backend instances together: older
versions assume a unique token row per user and do not enforce JWT purpose, so
they must not serve requests alongside the new version. Plan rollback against
the current data and security implications; do not simply restore an old binary
or delete session rows.
Legacy access JWTs do not contain the required purpose and stop authenticating after deployment. Existing web and mobile clients do not automatically use the refresh endpoint, so affected users will need to sign in again. The mobile app does not consistently recognize a server-side 401 as an expired session; plan user communication and account for pending offline data before the rollout. Other clients with a still-valid legacy refresh session may exchange it for a new access JWT. No cookie name, endpoint or response body changes are required.
Expired refresh rows older than seven days are removed in batches of at most 1,000 every five minutes, starting one hour after startup. Monitor database growth and cleanup capacity in deployments with sustained high login volume.
A complete list of endpoints is available here.
- Create new user (not activated):
POST /api/user/register
{
"email": "test@user.com",
"password": "password",
"name": "Test",
"surname": "User"
}- Confirm email:
POST /api/user/confirm_email
Insert token from the activation email message
{
"token": "2b7875bc-13ec-4cc3-bf75-21e9a5847d44"
}- Log in as admin:
POST /api/user/login
{
"username": "",
"password": ""
}- Activate user (logged in as admin):
POST /api/user/admin/execute/ACTIVATE_USER
Find the user ID in the database.
{
"id": "2"
}- Upload image:
POST /api/common/image
Body: form-data
Key: file
Value: select file
Response:
{
"status": "OK",
"data": {
"id": 1,
"storageKey": "b822e079-7584-4489-ac78-81292d0c2c8c",
"name": "roasted_coffee_beans.jpg",
"contentType": "image/jpeg",
"size": 599153
}
}- Create or update company:
POST /api/company/createorPUT /api/company/profile
{
"name": "Coffe company",
"abbreviation": "CCC",
"headquarters": {
"address": "Coffee street",
"city": "Java",
"state": "Java",
"zip": "1000",
"country": {
"id": "104"
}
},
"about": "Making great coffee",
"manager": "Mana Ger",
"webPage": "inatrace.org",
"email": "info@inatrace.org",
"logo": {
"storageKey": "b822e079-7584-4489-ac78-81292d0c2c8c"
}
}Response:
{
"status": "OK",
"data": {
"id": 1
}
}- Create or update product:
POST /api/product/createorPUT /api/product
{
"id":"integer",
"name":"string",
"photo":"ApiDocument",
"description":"string",
"origin": ApiProductOrigin,
"process": ApiProcess,
"responsibility": ApiResponsibility,
"sustainability": ApiSustainability,
"associatedCompanies": ApiProductCompany,
"company": ApiCompany,
"labels": ApiProductLabelValues,
"settings": ApiProductSettings,
"knowledgeBlog":"boolean"
}
Response is structured in the following way:
It always contains attribute status.
If response is successful, then status is equal to 'OK' and appropriate response can be found under data attribute.
If response is unsuccessful (see the above link for other statuses), then errorMessage attribute is returned.
Example of successful and unsuccessful response
{
"status": "OK",
"data": {
"id": 4,
"email": "example@example.com",
"name": "Example",
"surname": "Example",
"status": "ACTIVE",
"role": "SYSTEM_ADMIN",
"actions": [
"VIEW_USER_PROFILE",
"UPDATE_USER_PROFILE"
],
"companyIds": [
1
]
}
}
{
"status": "AUTH_ERROR",
"errorMessage": "Invalid credentials"
}
Currency service manages exchange rate data retrieval and currency conversion.
The service uses exchangeratesapi.io API for fetching currency conversion rates. It runs daily at 00:01 system time. The API is limited to 250 requests per day.
The service exposes methods convert and convertAtDate to convert between any two supported currencies. convert uses the latest localy stored rate. convertAtDate uses the rate at the specified date. If the exchange rate for the specified date is not stored locally it fetches it from the API.