{"openapi":"3.1.0","info":{"title":"Smartrplace Gateway API Proxy","description":"CMS-authorised access to gateway APIs, without gateway credentials","version":"0.1.0"},"paths":{"/gw/{gateway}/{upstream}":{"head":{"tags":["proxy"],"summary":"Call a gateway API as master_rest","description":"Forwards `GET`/`HEAD` to `{gateway_url}/{upstream}` authenticated as that gateway's\n`master_rest` user, once the CMS has confirmed that **you** may access the facility\nbehind it.\n\n**`gateway`** may be the subdomain (`jakob-sozien`), the bare gateway id (`19106`) or\nthe `gw`-prefixed id (`gw19106`) — all three resolve to the same site.\n\n**`upstream`** is the gateway path, which must start with one of the configured\nprefixes (default `/apimobile/monitoring/`; see `GET /health`). The query string is\nforwarded unchanged. Percent-encoding is preserved byte for byte, except that encoded\npath separators (`%2f`, `%5c`) and `..` segments are refused.\n\n**Credentials** — send exactly one of:\n\n* `Auth-Token: <CMS token>` — your own CMS token, from\n  `POST https://app.smartrplace.de/api/users/login`. Recommended: the proxy stores\n  nothing and the CMS's own 24 h expiry applies.\n* `Authorization: Bearer <CMS token>` — the same token, for clients that dislike\n  custom headers.\n* `X-Service-Token: <token>` — a named token for internal tooling, scoped to an\n  explicit gateway list. No CMS user involved.\n\n**Who gets through:** CMS super admins, callers with a **facility-level** role at the\ngateway's facility, and service tokens scoped to it. Tenancy-level callers (tenant,\nemployee, WEG owner) are refused even though the CMS grants them facility access —\n`master_rest` is exempt from the gateway's own per-room permission checks, so a\nforwarded response covers the whole site.\n\nExample:\n\n```\ncurl -H \"Auth-Token: $TOKEN\"      .../gw/jakob-sozien/apimobile/monitoring/datapoints\n```\n\nEvery call is written to the audit trail, denials included.","operationId":"proxy_gw__gateway___upstream__head","parameters":[{"name":"gateway","in":"path","required":true,"schema":{"type":"string","title":"Gateway"}},{"name":"upstream","in":"path","required":true,"schema":{"type":"string","title":"Upstream"}},{"name":"Auth-Token","in":"header","required":false,"schema":{"type":"string","default":"","title":"Auth-Token"}},{"name":"Authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}},{"name":"X-Service-Token","in":"header","required":false,"schema":{"type":"string","default":"","title":"X-Service-Token"}},{"name":"X-Forwarded-For","in":"header","required":false,"schema":{"type":"string","default":"","title":"X-Forwarded-For"}}],"responses":{"200":{"description":"The gateway's response, forwarded verbatim (streamed, content type and encoding preserved).","content":{"application/json":{"schema":{}}}},"401":{"description":"`unauthenticated` — no credentials, or the CMS rejected them."},"403":{"description":"`no_cms_access` / `tenancy_level_role` / `no_facility_role` / `service_token_scope` — the proxy's own decision. Also `path_not_allowed` when the path is outside the allowlist."},"404":{"description":"`unknown_gateway` — no such gateway, or the proxy holds no credentials for it. The two are deliberately indistinguishable."},"405":{"description":"The method is not proxied (P0 is read-only)."},"429":{"description":"`rate_limited` (too many requests for this caller, or too many failed authentications from this address) or `too_many_in_flight` (too many concurrent requests). Both carry `Retry-After`."},"502":{"description":"`gateway_unreachable` / `gateway_timeout` / `gateway_auth_failed` / `response_too_large` — the gateway failed, not you. Or `cms_unavailable`: no access decision could be made, which is never reported as a denial."},"503":{"description":"`gateway_busy` (too many in-flight requests for that gateway) or `credentials_unavailable`."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["proxy"],"summary":"Call a gateway API as master_rest","description":"Forwards `GET`/`HEAD` to `{gateway_url}/{upstream}` authenticated as that gateway's\n`master_rest` user, once the CMS has confirmed that **you** may access the facility\nbehind it.\n\n**`gateway`** may be the subdomain (`jakob-sozien`), the bare gateway id (`19106`) or\nthe `gw`-prefixed id (`gw19106`) — all three resolve to the same site.\n\n**`upstream`** is the gateway path, which must start with one of the configured\nprefixes (default `/apimobile/monitoring/`; see `GET /health`). The query string is\nforwarded unchanged. Percent-encoding is preserved byte for byte, except that encoded\npath separators (`%2f`, `%5c`) and `..` segments are refused.\n\n**Credentials** — send exactly one of:\n\n* `Auth-Token: <CMS token>` — your own CMS token, from\n  `POST https://app.smartrplace.de/api/users/login`. Recommended: the proxy stores\n  nothing and the CMS's own 24 h expiry applies.\n* `Authorization: Bearer <CMS token>` — the same token, for clients that dislike\n  custom headers.\n* `X-Service-Token: <token>` — a named token for internal tooling, scoped to an\n  explicit gateway list. No CMS user involved.\n\n**Who gets through:** CMS super admins, callers with a **facility-level** role at the\ngateway's facility, and service tokens scoped to it. Tenancy-level callers (tenant,\nemployee, WEG owner) are refused even though the CMS grants them facility access —\n`master_rest` is exempt from the gateway's own per-room permission checks, so a\nforwarded response covers the whole site.\n\nExample:\n\n```\ncurl -H \"Auth-Token: $TOKEN\"      .../gw/jakob-sozien/apimobile/monitoring/datapoints\n```\n\nEvery call is written to the audit trail, denials included.","operationId":"proxy_gw__gateway___upstream__head","parameters":[{"name":"gateway","in":"path","required":true,"schema":{"type":"string","title":"Gateway"}},{"name":"upstream","in":"path","required":true,"schema":{"type":"string","title":"Upstream"}},{"name":"Auth-Token","in":"header","required":false,"schema":{"type":"string","default":"","title":"Auth-Token"}},{"name":"Authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}},{"name":"X-Service-Token","in":"header","required":false,"schema":{"type":"string","default":"","title":"X-Service-Token"}},{"name":"X-Forwarded-For","in":"header","required":false,"schema":{"type":"string","default":"","title":"X-Forwarded-For"}}],"responses":{"200":{"description":"The gateway's response, forwarded verbatim (streamed, content type and encoding preserved).","content":{"application/json":{"schema":{}}}},"401":{"description":"`unauthenticated` — no credentials, or the CMS rejected them."},"403":{"description":"`no_cms_access` / `tenancy_level_role` / `no_facility_role` / `service_token_scope` — the proxy's own decision. Also `path_not_allowed` when the path is outside the allowlist."},"404":{"description":"`unknown_gateway` — no such gateway, or the proxy holds no credentials for it. The two are deliberately indistinguishable."},"405":{"description":"The method is not proxied (P0 is read-only)."},"429":{"description":"`rate_limited` (too many requests for this caller, or too many failed authentications from this address) or `too_many_in_flight` (too many concurrent requests). Both carry `Retry-After`."},"502":{"description":"`gateway_unreachable` / `gateway_timeout` / `gateway_auth_failed` / `response_too_large` — the gateway failed, not you. Or `cms_unavailable`: no access decision could be made, which is never reported as a denial."},"503":{"description":"`gateway_busy` (too many in-flight requests for that gateway) or `credentials_unavailable`."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/me":{"get":{"tags":["me"],"summary":"How the proxy sees your credentials","description":"The caller's identity as resolved from their credentials.","operationId":"me_me_get","parameters":[{"name":"Auth-Token","in":"header","required":false,"schema":{"type":"string","default":"","title":"Auth-Token"}},{"name":"Authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}},{"name":"X-Service-Token","in":"header","required":false,"schema":{"type":"string","default":"","title":"X-Service-Token"}}],"responses":{"200":{"description":"Your identity. `kind` is `cms` or `service`.","content":{"application/json":{"schema":{}}}},"401":{"description":"`unauthenticated`."},"502":{"description":"`cms_unavailable`."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/me/gateways":{"get":{"tags":["me"],"summary":"Gateways you can expect to reach","description":"Derived from your CMS facility roles in a single call, so it may be optimistic: the real decision is made per request against the CMS. Super admins and `*`-scoped service tokens get the whole store. The `via` field says why each entry is listed.","operationId":"my_gateways_me_gateways_get","parameters":[{"name":"Auth-Token","in":"header","required":false,"schema":{"type":"string","default":"","title":"Auth-Token"}},{"name":"Authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}},{"name":"X-Service-Token","in":"header","required":false,"schema":{"type":"string","default":"","title":"X-Service-Token"}}],"responses":{"200":{"description":"A hint list, plus the count.","content":{"application/json":{"schema":{}}}},"401":{"description":"`unauthenticated`."},"502":{"description":"`cms_unavailable`."},"503":{"description":"`credentials_unavailable` — no credential store."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/login":{"post":{"tags":["auth"],"summary":"Exchange CMS credentials for a proxy session token","description":"Forwards the credentials to the CMS and returns an opaque token to send in `Auth-Token` on subsequent calls. The token grants exactly what the CMS login grants — the identity behind it is re-resolved through the CMS on every request — and expires after `SESSION_TTL` (8 h by default).","operationId":"login_auth_login_post","parameters":[{"name":"X-Forwarded-For","in":"header","required":false,"schema":{"type":"string","default":"","title":"X-Forwarded-For"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}}},"responses":{"200":{"description":"`{token, expires_in, email}`.","content":{"application/json":{"schema":{}}}},"401":{"description":"`invalid_credentials` — the CMS rejected them."},"404":{"description":"`sessions_disabled` — this deployment does not offer login."},"429":{"description":"`rate_limited` — too many attempts from this address."},"502":{"description":"`cms_unavailable`."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/logout":{"post":{"tags":["auth"],"summary":"Invalidate a proxy session token","operationId":"logout_auth_logout_post","parameters":[{"name":"Auth-Token","in":"header","required":false,"schema":{"type":"string","default":"","title":"Auth-Token"}},{"name":"Authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}}],"responses":{"200":{"description":"`{closed: true|false}` — false if it was already gone.","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/metrics":{"get":{"tags":["operations"],"summary":"Process counters (super admins and service tokens)","operationId":"read_metrics_metrics_get","parameters":[{"name":"Auth-Token","in":"header","required":false,"schema":{"type":"string","default":"","title":"Auth-Token"}},{"name":"Authorization","in":"header","required":false,"schema":{"type":"string","default":"","title":"Authorization"}},{"name":"X-Service-Token","in":"header","required":false,"schema":{"type":"string","default":"","title":"X-Service-Token"}}],"responses":{"200":{"description":"Counters: decisions by reason, upstream statuses, cache effectiveness, rate-limit rejections, and per-gateway totals including credential staleness.","content":{"application/json":{"schema":{}}}},"401":{"description":"`unauthenticated`."},"403":{"description":"`forbidden` — not a super admin or service token."},"404":{"description":"`metrics_disabled`."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/health":{"get":{"summary":"Health","description":"Liveness plus the things that actually break: the credential store, the effective\npolicy, and any gateway whose stored ``master_rest`` password has gone stale.\n\nDeliberately does not call the CMS — a health check must not add load to a dependency,\nand CMS reachability shows up in the request logs anyway. ``HEAD`` is accepted because\nthat is what monitors and ``curl -I`` send.","operationId":"health_health_head","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Health Health Head"}}}}}},"head":{"summary":"Health","description":"Liveness plus the things that actually break: the credential store, the effective\npolicy, and any gateway whose stored ``master_rest`` password has gone stale.\n\nDeliberately does not call the CMS — a health check must not add load to a dependency,\nand CMS reachability shows up in the request logs anyway. ``HEAD`` is accepted because\nthat is what monitors and ``curl -I`` send.","operationId":"health_health_head","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Health Health Head"}}}}}}}},"components":{"schemas":{"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"LoginRequest":{"properties":{"email":{"type":"string","title":"Email","examples":["you@smartrplace.de"]},"password":{"type":"string","title":"Password","examples":["…"]}},"type":"object","required":["email","password"],"title":"LoginRequest","description":"CMS credentials. Forwarded once, never stored."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}},"servers":[{"url":"https://gateway-access.smartrplace.de","description":"Gateway API Proxy"}]}