{
  "openapi": "3.1.0",
  "info": {
    "title": "RENPHO Shared Session Bridge",
    "version": "0.5.0",
    "description": "Scale data API using the existing official-app session. No RENPHO login endpoint. Preserves scale measurements and raw goal datasets staticGoal, scaleGoalV2 and scaleGoalV3. Goal versions may overlap. Other device families are not imported; unimportedCollections reports remaining inventory. Missing inventory counts remain unknown. MCP also supports OAuth 2.1 with PKCE S256 and ChatGPT CIMD. OAuth access tokens only authorize MCP read tools; REST accepts Bridge API keys or the same-origin website session. Cookie-authenticated mutations require X-CSRF-Token; OAuth access tokens are never accepted for REST. The diagnostics endpoint returns safe metadata and bounded synchronization history. Personal keys created as read-only answer 403 read_only_key to any REST mutation; they can still use MCP read tools. When the API feature is off for an account, Bearer clients keep only status, sync, pairing code and connection routes. Repeated failed authentication from one client answers 429 with Retry-After. When RENPHO refuses a session that has not expired, status error is session_rejected and synchronization keeps retrying for about 30 minutes before the state becomes reconnect_required."
  },
  "servers": [
    {
      "url": "/"
    }
  ],
  "security": [
    {
      "ApiKey": []
    },
    {
      "WebsiteSession": []
    }
  ],
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "Bridge API key"
      },
      "WebsiteSession": {
        "type": "apiKey",
        "in": "cookie",
        "name": "__Host-renpho-account",
        "description": "Secure HttpOnly website session. Mutating requests also require the current X-CSRF-Token header and an allowed origin."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ]
      },
      "Status": {
        "type": "object",
        "properties": {
          "state": {
            "type": "string",
            "enum": [
              "not_connected",
              "connected",
              "ready",
              "sync_error",
              "reconnect_required",
              "disconnected"
            ]
          },
          "ownerId": {
            "type": [
              "string",
              "null"
            ]
          },
          "expiresAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "lastSyncedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "snapshotVersion": {
            "type": "integer",
            "format": "int64"
          },
          "records": {
            "type": "integer"
          },
          "inventory": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true
          },
          "collectionCounts": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            },
            "description": "Stored record count per raw goal dataset. Do not sum versions as unique goals."
          },
          "unimportedCollections": {
            "type": "object",
            "additionalProperties": {
              "type": [
                "integer",
                "null"
              ]
            },
            "description": "Inventory collections not fully imported; null is unknown, never zero."
          },
          "lastAttemptAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Start or completion time of the latest synchronization attempt; null if no attempt is known."
          }
        }
      },
      "Record": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string"
          },
          "id": {
            "type": "string",
            "description": "Lossless record identity; use this rather than parsing the raw numeric ID in JavaScript."
          },
          "profileId": {
            "type": [
              "string",
              "null"
            ]
          },
          "measuredAt": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64",
            "description": "Unix seconds, UTC."
          },
          "raw": {
            "type": "object",
            "additionalProperties": true,
            "description": "Original RENPHO fields, units and numeric precision, including unknown future fields. Use a lossless JSON parser for raw 64-bit numeric IDs."
          }
        }
      },
      "MeasurementPage": {
        "type": "object",
        "properties": {
          "snapshotVersion": {
            "type": "integer",
            "format": "int64"
          },
          "total": {
            "type": "integer"
          },
          "page": {
            "type": "integer"
          },
          "pageSize": {
            "type": "integer"
          },
          "nextPage": {
            "type": [
              "integer",
              "null"
            ]
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Record"
            }
          }
        }
      },
      "CollectionPage": {
        "type": "object",
        "properties": {
          "snapshotVersion": {
            "type": "integer",
            "format": "int64"
          },
          "total": {
            "type": "integer"
          },
          "page": {
            "type": "integer"
          },
          "pageSize": {
            "type": "integer"
          },
          "nextPage": {
            "type": [
              "integer",
              "null"
            ]
          },
          "items": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "description": "Original goal record, with unchanged fields, units and numeric precision."
            }
          },
          "kind": {
            "type": "string"
          }
        }
      },
      "SyncAttempt": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "startedAt": {
            "type": "string",
            "format": "date-time"
          },
          "completedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "durationMs": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64",
            "minimum": 0
          },
          "outcome": {
            "type": "string",
            "enum": [
              "running",
              "succeeded",
              "failed",
              "cancelled",
              "interrupted"
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "detail": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why RENPHO refused a session: http_401, http_403, code_401, code_403 or session_expired. Absent for other failures."
          },
          "records": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "description": "Cached measurement count at completion, not the number downloaded before a failure."
          },
          "snapshotVersion": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64"
          }
        }
      },
      "Diagnostics": {
        "type": "object",
        "description": "Tenant-scoped diagnostic metadata without owner/profile identifiers, measurements, raw inventory, API keys or session credentials. Attempts before history tracking was installed are unavailable.",
        "properties": {
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "state": {
            "type": "string",
            "enum": [
              "not_connected",
              "connected",
              "ready",
              "sync_error",
              "reconnect_required",
              "disconnected"
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "backgroundSyncAllowed": {
            "type": "boolean"
          },
          "freshness": {
            "type": "object",
            "properties": {
              "lastSyncedAt": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "lastAttemptAt": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "ageSeconds": {
                "type": [
                  "integer",
                  "null"
                ],
                "format": "int64",
                "minimum": 0
              },
              "nextSyncDueAt": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "description": "Scheduling estimate; null when background synchronization is disabled or the connection cannot be synchronized. This is not a promise of completion."
              }
            }
          },
          "snapshot": {
            "type": "object",
            "properties": {
              "version": {
                "type": "integer",
                "format": "int64"
              },
              "records": {
                "type": "integer",
                "minimum": 0
              },
              "collectionCounts": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                },
                "description": "Stored record count per raw goal dataset. Do not sum versions as unique goals."
              },
              "unimportedCollections": {
                "type": "object",
                "additionalProperties": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "description": "Inventory collections not fully imported; null is unknown, never zero."
              }
            }
          },
          "attempts": {
            "type": "array",
            "maxItems": 50,
            "items": {
              "$ref": "#/components/schemas/SyncAttempt"
            }
          }
        }
      }
    }
  },
  "paths": {
    "/healthz": {
      "get": {
        "operationId": "health",
        "security": [],
        "responses": {
          "200": {
            "description": "Service is running"
          }
        }
      }
    },
    "/v1/status": {
      "get": {
        "operationId": "getStatus",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Status"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Bridge account authentication required; OAuth access tokens are not accepted for REST.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Reconnect required, concurrent sync or stale snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Incomplete or changed upstream snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "RENPHO unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "504": {
            "description": "RENPHO timed out",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/connection": {
      "post": {
        "operationId": "attachSession",
        "description": "Validate the phone session and bind it to this Bridge account. Does not log in to RENPHO. Cannot replace the bound RENPHO owner with another account. The platform field selects the onboarding channel policy; a legacy omitted/null value requires both channels. New binding of an owner already associated with another tenant returns 409 connection_owned.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "properties": {
                  "token": {
                    "type": "string",
                    "writeOnly": true,
                    "maxLength": 16384
                  },
                  "platform": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "android",
                      "iphone",
                      null
                    ],
                    "description": "Optional source channel. Omitted/null is a legacy client with unknown source, accepted only when both Android and iPhone onboarding policies allow it."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Status"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Bridge account authentication required; OAuth access tokens are not accepted for REST.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Reconnect required, concurrent sync or stale snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Incomplete or changed upstream snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "RENPHO unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "504": {
            "description": "RENPHO timed out",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Account suspended or applicable onboarding channel disabled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "disconnect",
        "description": "Forget the local encrypted session, stop sync and retain cached measurements. Does not log out the official app.",
        "responses": {
          "204": {
            "description": "Success"
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Bridge account authentication required; OAuth access tokens are not accepted for REST.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Reconnect required, concurrent sync or stale snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Incomplete or changed upstream snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "RENPHO unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "504": {
            "description": "RENPHO timed out",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/sync": {
      "post": {
        "operationId": "synchronize",
        "description": "Download a complete scale snapshot, validate source counts before/after, then publish atomically. Cached data survives any incomplete run. An empty answer never replaces a saved history: it fails with empty_snapshot_refused and the cache stays until the owner confirms the deletion with /v1/sync/confirm-empty. Deletions in RENPHO are mirrored while at least one record remains. A manual run is limited to one per minute per account (Bridge:ManualSyncCooldownSeconds); an earlier repeat answers 429 sync_cooldown with Retry-After. Refusals that never reach RENPHO do not start the pause.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Status"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Bridge account authentication required; OAuth access tokens are not accepted for REST.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Reconnect required, concurrent sync or stale snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "sync_cooldown with Retry-After, service_busy, or the per-account request limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Incomplete or changed upstream snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "RENPHO unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "504": {
            "description": "RENPHO timed out",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/sync/confirm-empty": {
      "post": {
        "operationId": "confirmEmptyHistory",
        "description": "The owner confirms that every measurement was deleted in RENPHO. Allowed only while the latest attempt failed with empty_snapshot_refused. Bridge downloads again: if RENPHO is still empty the saved history and goal datasets are cleared and the snapshot version increases; if RENPHO now returns records they are saved as in a normal run. Download /v1/export first to keep a copy.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Status"
                }
              }
            }
          },
          "401": {
            "description": "Bridge account authentication required; OAuth access tokens are not accepted for REST.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Read-only key, account suspended or browser origin refused",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "empty_snapshot_not_pending, reconnect_required or sync_in_progress",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "service_busy",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Incomplete or changed upstream snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "RENPHO unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "504": {
            "description": "RENPHO timed out",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/profiles": {
      "get": {
        "operationId": "getProfiles",
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Bridge account authentication required; OAuth access tokens are not accepted for REST.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Reconnect required, concurrent sync or stale snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Incomplete or changed upstream snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "RENPHO unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "504": {
            "description": "RENPHO timed out",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "API subscription required for bearer access",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Account suspended or API access disabled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/measurements": {
      "get": {
        "operationId": "getMeasurements",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "1-based page, default 1."
          },
          {
            "name": "pageSize",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "1..100, default 50."
          },
          {
            "name": "profileId",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter within this caller only."
          },
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Inclusive Unix seconds."
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Inclusive Unix seconds."
          },
          {
            "name": "snapshotVersion",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Use the version returned on page 1. A changed snapshot returns 409; restart pagination."
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MeasurementPage"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Bridge account authentication required; OAuth access tokens are not accepted for REST.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Reconnect required, concurrent sync or stale snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Incomplete or changed upstream snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "RENPHO unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "504": {
            "description": "RENPHO timed out",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "API subscription required for bearer access",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Account suspended or API access disabled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/measurements/latest": {
      "get": {
        "operationId": "getLatest",
        "parameters": [
          {
            "name": "profileId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MeasurementPage"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Bridge account authentication required; OAuth access tokens are not accepted for REST.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Reconnect required, concurrent sync or stale snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Incomplete or changed upstream snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "RENPHO unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "504": {
            "description": "RENPHO timed out",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "API subscription required for bearer access",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Account suspended or API access disabled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/key/rotate": {
      "post": {
        "operationId": "rotateKey",
        "description": "Legacy connections only. Rotate the primary API key and revoke device keys and any personal keys. Return the new key once. Connections owned by a website account answer 409 key_managed_in_account; manage their keys in the cabinet.",
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Bridge account authentication required; OAuth access tokens are not accepted for REST.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Reconnect required, concurrent sync or stale snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Incomplete or changed upstream snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "RENPHO unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "504": {
            "description": "RENPHO timed out",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/export": {
      "get": {
        "operationId": "exportMeasurements",
        "description": "Download measurements and all stored goal datasets from one local read transaction. Concurrent synchronization does not mix versions. Original JSON numbers and fields are preserved. Collection versions can overlap; do not sum them as unique goals. Bearer access follows the API policy; website exports remain available to active Free accounts. One export per account runs at a time; across accounts builds are serialized for memory and downloads proceed in parallel (up to four).",
        "responses": {
          "200": {
            "description": "Complete JSON export with snapshotVersion, total, exportedAt, measurement items and goal collections.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "snapshotVersion": {
                      "type": "integer",
                      "format": "int64"
                    },
                    "total": {
                      "type": "integer"
                    },
                    "exportedAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Record"
                      }
                    },
                    "collections": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "additionalProperties": true
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Bridge account authentication required; OAuth access tokens are not accepted for REST."
          },
          "429": {
            "description": "An export of this account is already running, or service_busy with Retry-After when every download slot is taken",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "API subscription required for bearer access",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Account suspended or API access disabled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/onboarding": {
      "post": {
        "operationId": "onboardAndroidDevice",
        "security": [],
        "description": "Validates possession of an existing RENPHO phone session, creates or reuses that owner account, authorizes the client-generated random API key, and performs a complete cloud sync. No RENPHO login. Existing keys remain valid. Retry with the same key is idempotent. Maximum 20 supplementary device keys per account; key rotation revokes them. Limited to 6 requests per minute globally. Browser Origin is rejected. The platform field selects the onboarding channel policy; a legacy omitted/null value requires both channels. New binding of an owner already associated with another tenant returns 409 connection_owned.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token",
                  "apiKey"
                ],
                "properties": {
                  "token": {
                    "type": "string",
                    "writeOnly": true,
                    "maxLength": 16384
                  },
                  "apiKey": {
                    "type": "string",
                    "writeOnly": true,
                    "pattern": "^rph_[0-9a-fA-F]{64}$",
                    "description": "Generate 32 cryptographically random bytes and encode as rph_ + hex. Save privately before the first request."
                  },
                  "platform": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "android",
                      "iphone",
                      null
                    ],
                    "description": "Optional source channel. Omitted/null is a legacy client with unknown source, accepted only when both Android and iPhone onboarding policies allow it."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Status"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Reconnect required, concurrent sync or stale snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Incomplete or changed upstream snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "RENPHO unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "504": {
            "description": "RENPHO timed out",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Account suspended or applicable onboarding channel disabled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/download/android": {
      "get": {
        "operationId": "downloadAndroidHelper",
        "security": [],
        "responses": {
          "200": {
            "description": "Signed Android helper APK",
            "content": {
              "application/vnd.android.package-archive": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "description": "Helper not bundled with this deployment"
          }
        }
      }
    },
    "/v1/oauth/authorizations": {
      "get": {
        "summary": "List the current owner's active OAuth grants",
        "responses": {
          "200": {
            "description": "Successful response"
          },
          "401": {
            "description": "Bridge account authentication required; OAuth access tokens are not accepted for REST."
          }
        }
      }
    },
    "/v1/oauth/revoke": {
      "post": {
        "summary": "Revoke all OAuth grants of the current owner",
        "description": "Immediately rejects related OAuth access and refresh tokens. Does not alter RENPHO sessions, saved measurements or API keys.",
        "responses": {
          "200": {
            "description": "Successful response"
          },
          "401": {
            "description": "Bridge account authentication required; OAuth access tokens are not accepted for REST."
          }
        }
      }
    },
    "/v1/oauth/authorizations/{id}": {
      "delete": {
        "summary": "Revoke one OAuth grant belonging to the current owner",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Grant revoked"
          },
          "404": {
            "description": "Grant missing or belongs to another owner"
          },
          "409": {
            "description": "Concurrent change; retry"
          },
          "401": {
            "description": "Bridge account authentication required; OAuth access tokens are not accepted for REST."
          }
        }
      }
    },
    "/v1/collections/{kind}": {
      "get": {
        "operationId": "getCollection",
        "parameters": [
          {
            "name": "kind",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "staticGoal",
                "scaleGoalV2",
                "scaleGoalV3"
              ]
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "1-based page, default 1."
          },
          {
            "name": "pageSize",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "1..100, default 50."
          },
          {
            "name": "snapshotVersion",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Use the version returned on page 1. A changed snapshot returns 409; restart pagination."
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CollectionPage"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Bridge account authentication required; OAuth access tokens are not accepted for REST.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Reconnect required, concurrent sync or stale snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Incomplete or changed upstream snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "RENPHO unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "504": {
            "description": "RENPHO timed out",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Collection has not been synchronized or is unknown"
          },
          "402": {
            "description": "API subscription required for bearer access",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Account suspended or API access disabled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Paginated original goal dataset. Versions can overlap; use snapshotVersion on subsequent pages."
      }
    },
    "/v1/diagnostics": {
      "get": {
        "operationId": "getDiagnostics",
        "description": "Safe diagnostic summary and bounded persisted synchronization history. Website sessions can read their own report; bearer API access follows the API service policy. OAuth tokens are accepted only by the renpho_diagnostics MCP tool.",
        "responses": {
          "200": {
            "description": "Safe diagnostic summary",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Diagnostics"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "API subscription required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Account or API access disabled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/readyz": {
      "get": {
        "operationId": "ready",
        "security": [],
        "responses": {
          "200": {
            "description": "The database answers"
          },
          "503": {
            "description": "Not ready"
          }
        }
      }
    },
    "/v1/pairing-code": {
      "post": {
        "operationId": "createPairingCode",
        "description": "One-time code that links this connection to a website account. Valid for 10 minutes; a new code replaces the previous one. Available when the API feature is off.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "string",
                      "example": "ABCDE-12345"
                    },
                    "expiresIn": {
                      "type": "integer",
                      "description": "Seconds."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Bridge account authentication required; OAuth access tokens are not accepted for REST.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Read-only key or suspended account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/onboarding/iphone": {
      "post": {
        "operationId": "onboardIphoneTicket",
        "security": [],
        "description": "Used by the personal Proxyman rule. Spends a one-time ticket created in the cabinet, validates possession of the current RENPHO session, connects it to the ticket owner and performs a complete cloud sync. No RENPHO login. Limited to 6 requests per minute. Browser Origin is rejected.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token",
                  "ticket"
                ],
                "properties": {
                  "token": {
                    "type": "string",
                    "writeOnly": true,
                    "maxLength": 16384
                  },
                  "ticket": {
                    "type": "string",
                    "writeOnly": true,
                    "minLength": 64,
                    "maxLength": 64
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "state": {
                      "type": "string"
                    },
                    "records": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid, used or expired ticket",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Account suspended, iPhone channel disabled or browser Origin",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Reconnect required, concurrent sync or connection owned by another account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Incomplete or changed upstream snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "RENPHO unavailable or session temporarily rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "504": {
            "description": "RENPHO timed out",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "x-oauth": {
    "discovery": "/.well-known/oauth-authorization-server",
    "protectedResourceMetadata": "/.well-known/oauth-protected-resource/mcp",
    "registration": "CIMD, verified chatgpt.com metadata only",
    "tokenEndpointAuthentication": "none with PKCE S256",
    "scope": "renpho.read",
    "refreshScope": "offline_access",
    "accessTokenLifetimeSeconds": 900,
    "refreshTokenLifetimeDays": 30
  }
}
