{"openapi":"3.1.0","info":{"title":"Earth Resources Universal Ingestion API","version":"1.0.0","description":"Multi-transport resource registration, telemetry, command, event replay, and audit API."},"servers":[{"url":"https://earthmqtt.space"}],"security":[{"bearerAuth":[]}],"paths":{"/healthz":{"get":{"security":[],"summary":"Liveness probe","responses":{"200":{"description":"Alive"}}}},"/readyz":{"get":{"security":[],"summary":"Storage readiness probe","responses":{"200":{"description":"Ready"},"503":{"description":"Not ready"}}}},"/v1/public-config":{"get":{"security":[],"summary":"Connection parameters for browser clients","description":"Public. Reports the endpoints and ports a console or dashboard needs to build correct connection strings.","responses":{"200":{"description":"Public configuration"}}}},"/v1/tenants":{"get":{"summary":"List tenants","description":"Admin only.","responses":{"200":{"description":"Tenant page"}}}},"/v1/tenants/{tenantId}":{"delete":{"summary":"Revoke a tenant credential","description":"Admin only. Resources and their events are left untouched.","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Revocation result"}}}},"/v1/spaces":{"get":{"summary":"List the spaces in a domain","description":"The default space `-` is implicit and always reported.","parameters":[{"name":"domain","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Space page"}}},"post":{"summary":"Create a space","description":"Admin or the domain's own tenant key. Clients enter a space by naming it in the MQTT username.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSpace"}}}},"responses":{"201":{"description":"Created"}}}},"/v1/spaces/{space}":{"delete":{"summary":"Delete a space","parameters":[{"name":"space","in":"path","required":true,"schema":{"type":"string"}},{"name":"domain","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Deletion result"}}}},"/v1/groups":{"get":{"summary":"List groups","description":"An admin sees every group; a tenant sees the ones it belongs to.","responses":{"200":{"description":"Group page"}}},"post":{"summary":"Create a group or replace it","description":"Admin only. A group is a domain several tenants share.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateGroup"}}}},"responses":{"201":{"description":"Created"}}}},"/v1/groups/{groupId}":{"put":{"summary":"Replace a group's members","parameters":[{"name":"groupId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Updated"},"404":{"description":"Not found"}}},"delete":{"summary":"Delete a group","parameters":[{"name":"groupId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deletion result"}}}},"/v1/ws-tickets":{"post":{"summary":"Exchange a key for a single-use WebSocket ticket","description":"Browsers cannot set Authorization on an upgrade; pass the returned ticket as ?ticket= on /v1/ws. Expires within seconds and is destroyed on first use.","responses":{"201":{"description":"Ticket issued"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/v1/resources":{"post":{"summary":"Register or rotate a resource credential","description":"Admin or tenant token, scoped to that tenant. The plaintext apiKey is returned once.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterResource"}}}},"responses":{"201":{"description":"Registered"},"401":{"$ref":"#/components/responses/Unauthorized"}}},"get":{"summary":"List resources","parameters":[{"$ref":"#/components/parameters/TenantId"},{"$ref":"#/components/parameters/Limit"}],"responses":{"200":{"description":"Resource page"}}}},"/v1/resources/{resourceId}":{"get":{"summary":"Get a resource snapshot","parameters":[{"$ref":"#/components/parameters/ResourceId"},{"$ref":"#/components/parameters/TenantId"}],"responses":{"200":{"description":"Resource snapshot"},"404":{"description":"Not found"}}},"delete":{"summary":"Delete a resource credential and snapshot","description":"Admin or tenant token. Recorded events remain until the retention window expires.","parameters":[{"$ref":"#/components/parameters/ResourceId"},{"$ref":"#/components/parameters/TenantId"}],"responses":{"200":{"description":"Deletion result"}}}},"/v1/telemetry":{"post":{"summary":"Ingest telemetry durably","description":"Returns only after the reliable event has been persisted. Reusing eventId is idempotent.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Telemetry"}}}},"responses":{"202":{"description":"Persisted"},"400":{"description":"Invalid envelope"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/v1/resources/{resourceId}/events":{"get":{"summary":"Replay durable events","parameters":[{"$ref":"#/components/parameters/ResourceId"},{"$ref":"#/components/parameters/TenantId"},{"name":"afterSequence","in":"query","schema":{"type":"integer","minimum":0}},{"$ref":"#/components/parameters/Limit"}],"responses":{"200":{"description":"Reliable event page or snapshot-required result"}}}},"/v1/resources/{resourceId}/commands":{"post":{"summary":"Durably issue a command","description":"Admin or tenant token. Persistence succeeds independently of whether a resource is currently connected.","parameters":[{"$ref":"#/components/parameters/ResourceId"},{"$ref":"#/components/parameters/TenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Command"}}}},"responses":{"202":{"description":"Command persisted and fan-out attempted"}}}},"/v1/audit":{"get":{"summary":"List audit records","parameters":[{"$ref":"#/components/parameters/TenantId"},{"$ref":"#/components/parameters/Limit"}],"responses":{"200":{"description":"Audit page"}}}},"/mcp":{"post":{"summary":"MCP Streamable HTTP messages","responses":{"200":{"description":"MCP response"},"202":{"description":"MCP notification accepted"}}},"get":{"summary":"MCP SSE stream","responses":{"200":{"description":"SSE stream"}}},"delete":{"summary":"Close MCP session","responses":{"200":{"description":"Closed"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"parameters":{"TenantId":{"name":"tenantId","in":"query","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$"}},"ResourceId":{"name":"resourceId","in":"path","required":true,"schema":{"type":"string"}},"Limit":{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":1000,"default":100}}},"schemas":{"CreateSpace":{"type":"object","required":["space"],"properties":{"domain":{"type":"string"},"space":{"type":"string"},"name":{"type":"string"}}},"CreateGroup":{"type":"object","required":["groupId"],"properties":{"groupId":{"type":"string"},"name":{"type":"string"},"members":{"type":"array","items":{"type":"string"}}}},"RegisterResource":{"type":"object","required":["tenantId","resourceId"],"properties":{"tenantId":{"type":"string"},"resourceId":{"type":"string"},"name":{"type":"string"},"spaces":{"type":"array","items":{"type":"string"},"description":"Spaces this resource may connect to; defaults to the default space only."},"metadata":{"type":"object","additionalProperties":true}}},"Telemetry":{"type":"object","required":["eventId","data"],"properties":{"tenantId":{"type":"string"},"resourceId":{"type":"string"},"eventId":{"type":"string","maxLength":200},"timestamp":{"type":"number"},"data":{"type":"object","additionalProperties":true}}},"Command":{"type":"object","required":["command"],"properties":{"eventId":{"type":"string"},"expiresAt":{"type":"number"},"command":{"type":"object","additionalProperties":true}}}},"responses":{"Unauthorized":{"description":"Missing or invalid bearer token"}}}}