Skip to content

API Documentation ​

IoT DC3's REST API docs are generated from code annotations, and each center service exposes its own online docs. By the end of this page you'll be able to open each center's online docs in development, walk through the login flow with the default credentials (fetch salt → fetch token → call with auth headers), read the CRUD path conventions, and see how the x-dc3-ai risk metadata on each endpoint feeds into AI/MCP tools.

You're about to call or debug a backend API. If you need to get the environment running first, see First Device. For the tenants and permissions behind those auth headers, see Auth · Tenant · RBAC.

Where the docs come from: annotation-generated, per-center ​

There are no hand-written API spec files. Every endpoint's title, parameters, and request/response models come from springdoc-openapi annotations on the Controllers (@Tag, @Operation, @Parameter, @Schema). Each center service emits its own OpenAPI JSON at runtime on the WebFlux stack.

Four business centers expose their own docs — Auth Center (dc3-center-auth), Manager Center (dc3-center-manager), Data Center (dc3-center-data), and Agentic Center (dc3-center-agentic). The Gateway (dc3-gateway) has no business Controllers of its own. Its configuration still declares a service dropdown pointing at /v3/api-docs/{svc} via springdoc.swagger-ui.urls, but the gateway aggregation routes behind it have been removed, so that entry point is currently unusable (see the warning below).

Four business centers · annotations generate at runtimeGateway aggregation · the single entryConsumersspringdoc renders at runtimeper-center OpenAPI JSONHTTP :8000pull JSONx-dc3-ai extensionrisk policy persistedController annotations: @Tag · @Operation · @Parameter · @Schema + x-dc3-ai risk extensiondc3-center-auth :8300/auth/v3/api-docsdc3-center-manager :8400/manager/v3/api-docsdc3-center-data :8500/data/v3/api-docsdc3-center-agentic :8600/agentic/v3/api-docsdc3-gateway :8000springdoc.swagger-ui.urls merge · /v3/api-docs/{svc} flattens base-pathsDeveloper browserswagger-ui.html · Authorize headersmake openapiexport JSON → build/openapiMCP aggregatorreads x-dc3-ai risk metadatadc3_mcp_tool_catalogriskLevel · destructive · idempotent · openWorldThe pro profile disables springdoc in each application-pro.yml: api-docs.enabled=false · swagger-ui.enabled=false — the endpoints do not exist, nothing is exposedAnnotations / human entryCenter services / gatewayToolingMCP / securityTool catalog tableExport / aggregate read

Grouping works in two layers. dc3-common-web's SpringDocConfig supplies the global metadata — title, version, contact, license, security schemes. Each business module then declares a GroupedOpenApi Bean under its already-scanned package, so only that module's Controllers are scanned. Each center service prepends its spring.webflux.base-path ( e.g. /auth, /manager) to the doc path, giving paths like /auth/v3/api-docs. You must include that base-path and hit each center directly — the gateway no longer provides an aggregation path that flattens it away.

dc3-center-single mode

dc3-center-single packs multiple business modules into one process, so its Swagger UI shows several groups at once. That's expected, not duplicated configuration.

Access entry points ​

In development, hit each center's Swagger UI directly (remember the base-path prefix). Exporting or debugging the contract likewise goes through each center's /{svc}/v3/api-docs.

TargetURL
Auth Center direct (preferred)http://<auth>:8300/auth/swagger-ui.html
Manager Center direct (preferred)http://<manager>:8400/manager/swagger-ui.html
Data Center direct (preferred)http://<data>:8500/data/swagger-ui.html
Agentic Center direct (preferred)http://<agentic>:8600/agentic/swagger-ui.html
Single-center OpenAPI JSONhttp://<center>:<port>/<svc>/v3/api-docs

The gateway aggregation entry point is currently unusable

dc3-gateway's application.yml still configures an aggregated Swagger UI pointing at /v3/api-docs/{svc} (http://<gateway>:8000/swagger-ui.html), but the corresponding aggregation proxy routes were removed from dc3-common/dc3-common-gateway's application-gateway.yml (the surviving routes are the OAuth and /api/v3/** business routes). Every entry in that UI's service dropdown fails with "Failed to load API definition". Until the aggregation routes are restored, always connect to each center directly.

Login and authentication: fetch salt → fetch token → send X-Auth-* headers ​

Public endpoints like /api/v3/auth/token/** (fetch salt, generate token, change password) are open. Every other business API behind the gateway requires three auth headers: X-Auth-Tenant, X-Auth-Login, and X-Auth-Token.

Login is a two-step handshake. First, request a random salt from the server using the username and tenant (use within 5 minutes as a guideline; the server does not enforce the timeout). Then submit the plaintext password together with that salt to exchange for a token (valid 12 hours). The salt participates in neither the password hash nor the JWT signature — the token's HMAC-SHA256 signing key is derived solely from the server-side DC3_SECURITY_KEY (environment variable, falling back to the dc3.security.key system property); the salt is just a one-time random string the handshake requires you to echo back. The password itself is submitted in plaintext (relies on HTTPS for transport protection) and verified by the backend PasswordUtil.verify using Argon2id (falling back to BCrypt when Argon2 is unavailable).

① Discover aggregated docs② Fetch salt (public)③ Exchange token (public)④ Call business API with auth headersswagger-ui.urls service dropdownrandom salt (use within ~5 min)verify password: Argon2id / BCryptsalt + DC3_SECURITY_KEY → HMAC-SHA256 signingvalidate token → HMAC signinject X-Auth-PrincipalR<T> unified responseGET /swagger-ui.htmlaggregated UI (four groups)POST /api/v3/auth/token/salt {tenant, name}/auth/token/saltR<String> saltPOST /api/v3/auth/token/generate {tenant, name, salt, password}/auth/token/generate (plaintext password · over HTTPS)JWT (valid 12h)tokenPOST /api/v3/manager/device/listX-Auth-Tenant · X-Auth-Login · X-Auth-Tokenforward (with tenant context)R<T> responseClientcurl / dc3 CLI / Swagger UIdc3-gateway:8000 · the only HTTP entrydc3-center-auth:8300 · salt / token issuingdc3-center-manager:8400 · business API sampleOnly /api/v3/auth/token/** is public · the password travels in plaintext relying on HTTPS · the salt never hashes the password, it only keys the JWT HMAC with DC3_SECURITY_KEYClientGateway / center servicesRequestResponseX-Auth-Tenant · X-Auth-Login · X-Auth-Token → Auth header trio

A real call looks like this (values are illustrative only; default/dc3 are the tenant and user shipped in the seed data):

bash
# 1. Fetch salt
curl -X POST http://localhost:8000/api/v3/auth/token/salt \
  -H 'Content-Type: application/json' \
  -d '{"tenant":"default","name":"dc3"}'
# → 2026.10 image line (and main) returns JSON object {"salt":"f3a9c1..."} (no R envelope); 2026.9 and earlier image lines return a bare text/plain salt. Use within 5 minutes

# 2. Submit the plaintext password together with the salt (HTTPS protects transport) to exchange for a token
curl -X POST http://localhost:8000/api/v3/auth/token/generate \
  -H 'Content-Type: application/json' \
  -d '{"tenant":"default","name":"dc3","salt":"f3a9c1...","password":"<plaintext password>"}'
# → 2026.10 image line (and main) returns JSON object {"token":"eyJ..."} (no R envelope); 2026.9 and earlier image lines return a bare text/plain token. Both set an httpOnly cookie; valid 12 hours

# 3. Call a business API with the auth headers
curl -X POST http://localhost:8000/api/v3/manager/device/list \
  -H 'X-Auth-Tenant: default' \
  -H 'X-Auth-Login: dc3' \
  -H 'X-Auth-Token: {"salt":"f3a9c1...","token":"eyJ..."}' \
  -H 'Content-Type: application/json' \
  -d '{"offset":0,"limit":10}'
bash
# The CLI wraps the whole process: fetch salt → exchange token → save credentials
dc3 config set gateway http://localhost:8000
dc3 auth login --tenant default --username dc3

To debug a protected endpoint in the Swagger UI, click Authorize in the top-right corner and fill in the auth headers per the table below:

HeaderExample value
X-Auth-Tenantdefault
X-Auth-Logindc3
X-Auth-Token{"salt":"...","token":"..."}

Never write real credentials into docs/logs/issues

Tokens, passwords, salts, and API keys must never be committed to documentation, commit history, or tickets. Hash values and tokens in examples are replaced with placeholders.

CRUD path conventions: the verb reflects result cardinality ​

All business APIs follow one naming rule — the verb on the HTTP path, the Java method, the gRPC RPC, and the frontend function all reflect the cardinality of the returned result. Use getXxx to read a single record, listXxx to read a collection, and the write triad add/update/delete to write. The path alone tells you whether a call returns one record or many, and whether it reads or writes.

ActionJava methodHTTP pathgRPC RPCFrontend function
Single recordgetXxx(...)/get_xxxGetXxxgetXxx(...)
CollectionlistXxx(...)/list_xxxListXxxlistXxx(...)
Createadd(BO)/addn/aaddXxx(...)
Updateupdate(BO)/updaten/aupdateXxx(...)
Deletedelete(Long)/deleten/adeleteXxx(...)

Reserved verb semantics

There is no select* family in the codebase at all — raw persistence in the dc3-db-* storage layer (R2dbc*Store) also uses the get/list/insert/update/delete verbs, and select* never appears on Service/Controller/Facade either. Business deletion always uses delete*. find*/query*/fetch* aren't used as primary CRUD verbs.

Here's a real example from Manager Center — the "add device" step of the golden path. The endpoint is POST /api/v3/manager/device/add, the request body is DeviceVO with key fields deviceName, driverId, profileId, and enableFlag (the string "ENABLE"); on success it returns 201 Created with the created DeviceVO (including its id). The matching delete endpoint DELETE /api/v3/manager/device/delete takes both id and an optimistic-lock version query parameter. The endpoint requires the device:add permission (@PreAuthorize("@perm.can('device', 'add')")). APIs return business objects or page objects directly — there is no uniform envelope. Pagination endpoints (like device/list) take flat offset/limit fields in the request body (zero-based offset, defaults offset=0 and limit=50, max 200) and respond with an OffsetPage (items/offset/limit/total/hasNext). Note that a nested page object is silently ignored — the global fail-on-unknown-properties=false doesn't error, but pagination falls back to the defaults. Errors use RFC 9457 problem details (application/problem+json).

x-dc3-ai: risk annotations for AI/MCP tools ​

An endpoint's @Operation can carry an x-dc3-ai OpenAPI extension that describes, through four boolean/enum properties, what risk a call carries for an AI agent. This metadata isn't a human-facing comment — the MCP tool catalog aggregator reads it, persists it into the dc3_mcp_tool_catalog table, and from there decides whether a tool is visible to a given AI connection in tools/list and whether invoking it needs a second confirmation.

java
@Extension(name = "x-dc3-ai", properties = {
    @ExtensionProperty(name = "riskLevel",   value = "MEDIUM"),  // LOW / MEDIUM / HIGH
    @ExtensionProperty(name = "destructive", value = "false"),   // whether it destroys data/config
    @ExtensionProperty(name = "idempotent",  value = "false"),   // whether it is safe to retry
    @ExtensionProperty(name = "openWorld",   value = "true")     // whether it reaches the external/physical world
})

What each property means:

  • riskLevel: LOW/MEDIUM/HIGH, annotated by hand per verb-semantic convention (not auto-derived from the HTTP method). By convention delete is HIGH, add/update are MEDIUM, and get/list are LOW. The final value comes from the hand-written annotation on each @Operation; the aggregator only falls back to HIGH when the annotation is missing or invalid. HIGH-risk tools are hidden from AI by default — they must be explicitly enabled and need a two-phase confirmation when invoked.
  • destructive: whether the call destroys existing data or settings (e.g. changing a password, revoking a token).
  • idempotent: whether repeating the call with the same parameters is safe (which decides whether it can be auto-retried after a failure).
  • openWorld: whether it reaches external systems or physical devices beyond the platform (e.g. dispatching a write command).

Take Auth Center's TokenController. The fetch-salt endpoint is annotated riskLevel=LOW, destructive=false, idempotent=false, openWorld=false, while generate-token is annotated riskLevel=HIGH. Both are public endpoints hidden from the AI tool catalog (hidden=true), but their risk levels are still faithfully distinguished. The Agentic Center's POST /api/v3/agentic/chat/completions is annotated riskLevel=MEDIUM, destructive=false, idempotent=false, openWorld=true.

The aggregator also derives read_only_hint from the HTTP method (GET → 1, POST → 0) and persists all hint bits ( destructive_hint/idempotent_hint/open_world_hint/read_only_hint, valued 0/1) alongside risk_level. The tool risk an AI agent sees through MCP is the final rendering of these annotations. For the full MCP tool exposure, filtering, and confirmation mechanism, see AI Agent / MCP Integration.

Exporting OpenAPI JSON ​

When you need an offline contract snapshot, or input for a client code generator, export each center's OpenAPI JSON from a running dev/test stack. Note that make openapi (dc3/bin/export_openapi.sh) fetches through the gateway aggregation path /v3/api-docs/{svc} by default — those routes are gone, so all four services return 404. Until the aggregation routes are restored, export directly from each center:

bash
curl -s http://localhost:8300/auth/v3/api-docs    -o openapi-auth.json
curl -s http://localhost:8400/manager/v3/api-docs -o openapi-manager.json
curl -s http://localhost:8500/data/v3/api-docs    -o openapi-data.json
curl -s http://localhost:8600/agentic/v3/api-docs -o openapi-agentic.json

Documentation requirements when adding an API ​

When you add a backend API, documentation isn't an afterthought — the annotations are the doc source:

  1. Add @Tag(name = "...", description = "...") to the Controller class.
  2. Add @Operation(summary = "...", description = "...") to the method. The summary should follow the CRUD verb convention (add/delete/update/getXxx/listXxx).
  3. Add @Parameter for path, query, and request-body parameters.
  4. Add @Schema(description = ...) to request/response DTO fields, with example and requiredMode = REQUIRED where needed.
  5. For endpoints callable by AI/MCP, add the x-dc3-ai extension reflecting the real risk. Once done, run make validate-annotations locally to check annotation completeness (the five AnnotationGateTests — Controller/Manager/Data/Agentic/Auth — the same gate cases CI runs in its unit-test job).
  6. When adding a new business module, add its GroupedOpenApi Bean (the group is exposed under the center's own base-path; no gateway-side configuration is needed).

Annotation text must always be in English

The summary/description in annotations is user-visible code text and, per engineering rules, must be written in English. Don't put sensitive values such as apiKey, password, secret, or token in @Schema example fields.

Disable Swagger / OpenAPI exposure in production

API documentation is only available in the dev, test, and pre environments. In production (the pro profile) each center service disables it in its own application-pro.yml (one each for auth/manager/data/agentic/single):

yaml
springdoc:
  api-docs:
    enabled: false
  swagger-ui:
    enabled: false

The shared application-web.yml only sets springdoc's baseline paths — it isn't responsible for disabling, and its comments say so. In production the springdoc endpoints simply don't exist, so no documentation content is ever exposed.

Further reading ​

  • Auth · Tenant · RBAC — the salt/token/HMAC behind the auth headers, plus tenant isolation and the permission model
  • First Device — walk the golden path with the dc3 CLI and actually exercise these APIs
  • AI Agent / MCP Integration — how the x-dc3-ai metadata becomes MCP tool risk policy and two-phase confirmation
  • Testing — how API contract and integration tests verify these paths

Released under the AGPL-3.0 License