External API Contract — Northbound / Public (REST)

*Status:* v1.0 authoritative · Owner: Backend team (CTO ruling) · Last reviewed: 2026-06-26 · Review cadence: quarterly

This is the outward-facing API consumed by other systems — OSS/BSS, integrators, partner services (repos: ft-northbound-api, ft-service-api, ft-provision-api).

Unlike the internal UI↔BE contract, it is proper REST and its living contract is the OpenAPI spec (springdoc / Swagger UI) — that is what external consumers read.

The rules below are the FT conventions that spec must follow. See also Java standard §J12.

Two surfaces, don’t mix them. Internal UI↔BE = POST-RPC (that contract). External = this page.
ext-contract-flow

The contract

Element Shape Notes

Style

REST — @RestController + @RequestMapping("api/<Resource>"); verb by semantics

<Resource> is PascalCase (api/Device, api/Provision, api/Task); unversioned (no /v1), additive-only

DTOs

request *RestRequest, response *RestResponse; list = Get<Resource>Response wrapping List<…​Dto>

no ICollection envelope (that’s the internal surface)

Ack / error envelope

StatusResponse { Integer errorCode, String message }errorCode 100 = success, 20X = failed

the external catalog — distinct from the internal 90xx (don’t mix); ref: ft-provision-api

Error transport

a real HTTP status (400/404/409/401/500) plus the StatusResponse body

external layer (J12); ❌ not "always 200"

Auth

Authorization: Bearer <JWT> (Spring Security); 401/403 on failure

see J14

Docs

OpenAPI via springdoc — @Tag / @Operation / @Schema; Swagger UI is the consumer-facing contract

a public endpoint without OpenAPI is incomplete

Operations — verb per kind (REST)

Operation Verb Path & body Response

List / query

GET (simple) or POST (filtered)

api/<Resource> (+ filter body for POST)

Get<Resource>Response / List<Dto>

Get by id

GET or POST

api/<Resource> with id (path or body)

*RestResponse

Create

POST

api/<Resource> *RestRequest

*RestResponse / StatusResponse

Update

PUT

api/<Resource> *RestRequest

*RestResponse / StatusResponse

Delete

DELETE

api/<Resource> (may carry a *RestRequest body)

StatusResponse

Action / RPC-ish

POST

api/<Resource> (e.g. api/Rpc, reprovision) *RestRequest

*RestResponse

The northbound surface is verb-by-semantics but action-heavy — many operations are POST/PUT with a request DTO in the body (not resource-id-in-path REST). Use GET for simple, side-effect-free reads.

Validation & errors

Implemented per the Java standard — see §J12 HTTP APIs: @Valid/@Validated at the boundary, one global @RestControllerAdvice per service, ❌ no per-controller try/catch, ❌ never leak stack traces. On this surface the advice maps exceptions → real HTTP status + StatusResponse.

Stability & change control

This API is consumed by outside systems — treat changes as breaking-by-default:

  1. No versioning — additive-only (CTO ruling). Paths stay api/<Resource> (no /v1, no version header). The API evolves only additively — new optional fields and new endpoints/resources are safe; you may never remove/rename a field or change a verb/status on an existing endpoint. A genuinely breaking change ships as a new resource/endpoint (the old one keeps working). Compatibility is held by never breaking, not by versioning.

  2. Error catalog is separate (CTO ruling). This surface owns StatusResponse.errorCode (100 = success / 20X = failed); it is deliberately distinct from the internal 90xx catalog (Internal API). ❌ Don’t reuse internal 90xx codes here, and don’t leak external codes into the internal surface.

  3. The OpenAPI spec is the contract — regenerate and review it on every change.