{
  "openapi": "3.1.0",
  "info": {
    "title": "Mouve Public API",
    "version": "1.0.0",
    "summary": "The curated public REST surface of Mouve.",
    "description": "Every endpoint is authenticated with a bearer credential — an OAuth 2.1\naccess token or a personal access token — and authorized by exact-match\nscopes: no hierarchy, and write does not imply read.\n\nErrors are RFC 9457 problem documents whose `code` comes from a frozen,\nappend-only registry (version 3).\nMatch on `code`, never on `detail`.\n\nThis document is generated from the controllers, the validated request\nDTOs, and the shared wire contract that serve the API. It is not\nmaintained by hand.",
    "termsOfService": "https://developers.mouve.it/docs/terms",
    "contact": {
      "name": "Mouve developer support",
      "url": "https://developers.mouve.it/docs"
    }
  },
  "servers": [
    {
      "url": "https://api.mouve.it",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "me",
      "description": "The account the credential acts for."
    },
    {
      "name": "users",
      "description": "Other people: profiles by handle, and the posts they have published."
    },
    {
      "name": "posts",
      "description": "Creating, reading, and deleting posts and replies."
    },
    {
      "name": "timeline",
      "description": "The authorizing user's own reading surfaces."
    }
  ],
  "paths": {
    "/public/v1/me": {
      "get": {
        "operationId": "Me_me",
        "tags": [
          "me"
        ],
        "summary": "Get the authorizing user",
        "description": "Returns the public identity of the account the credential acts for. Use it to confirm which account a token belongs to when connecting an integration, and to obtain the `username` that appears in post permalinks. Never returns email, date of birth, sex, height, or weight — those fields are absent from every response on this surface, under every scope.",
        "security": [
          {
            "oauth2": [
              "profile.read"
            ]
          },
          {
            "personalAccessToken": [
              "profile.read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicMeDto"
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "`UNAUTHENTICATED`, `TOKEN_EXPIRED`, `TOKEN_REVOKED`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "403": {
            "description": "`INSUFFICIENT_SCOPE`, `TOKEN_NOT_PERMITTED`, `ACCOUNT_SUSPENDED`, `ONBOARDING_REQUIRED`, `CLIENT_DISABLED`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "404": {
            "description": "`NOT_FOUND`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "429": {
            "description": "`RATE_LIMITED`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "500": {
            "description": "`INTERNAL`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          }
        }
      }
    },
    "/public/v1/users/{username}": {
      "get": {
        "operationId": "Users_byUsername",
        "tags": [
          "users"
        ],
        "summary": "Get a user by username",
        "description": "Looks a user up by their handle, with or without a leading `@`. `private: true` means the account gates its content behind follow approval — its posts list will be empty for anyone it has not approved. A UUID in this position is rejected: this route takes handles only.",
        "security": [
          {
            "oauth2": [
              "profile.read"
            ]
          },
          {
            "personalAccessToken": [
              "profile.read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "username",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicUserDto"
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "`VALIDATION_FAILED`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "401": {
            "description": "`UNAUTHENTICATED`, `TOKEN_EXPIRED`, `TOKEN_REVOKED`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "403": {
            "description": "`INSUFFICIENT_SCOPE`, `TOKEN_NOT_PERMITTED`, `ACCOUNT_SUSPENDED`, `ONBOARDING_REQUIRED`, `CLIENT_DISABLED`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "404": {
            "description": "`NOT_FOUND`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "429": {
            "description": "`RATE_LIMITED`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "500": {
            "description": "`INTERNAL`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          }
        }
      }
    },
    "/public/v1/users/{username}/posts": {
      "get": {
        "operationId": "Users_userPosts",
        "tags": [
          "users"
        ],
        "summary": "List a user's posts",
        "description": "Newest first, cursor-paginated. Every row is visibility-checked against the authorizing user, so a private account you do not follow yields an empty page rather than an error — an empty page is not proof the account has never posted. Cursors are bound to the author id, so a handle change does not detach an in-flight cursor from its result set.",
        "security": [
          {
            "oauth2": [
              "posts.read"
            ]
          },
          {
            "personalAccessToken": [
              "posts.read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "username",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 512
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicPostListDto"
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "`VALIDATION_FAILED`, `INVALID_CURSOR`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "401": {
            "description": "`UNAUTHENTICATED`, `TOKEN_EXPIRED`, `TOKEN_REVOKED`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "403": {
            "description": "`INSUFFICIENT_SCOPE`, `TOKEN_NOT_PERMITTED`, `ACCOUNT_SUSPENDED`, `ONBOARDING_REQUIRED`, `CLIENT_DISABLED`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "404": {
            "description": "`NOT_FOUND`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "429": {
            "description": "`RATE_LIMITED`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "500": {
            "description": "`INTERNAL`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          }
        }
      }
    },
    "/public/v1/posts": {
      "post": {
        "operationId": "Posts_create",
        "tags": [
          "posts"
        ],
        "summary": "Create a post or a reply",
        "description": "Send `audience` for a top-level post — it is required and never defaulted. Send `in_reply_to_post_id` for a reply, and then `audience` is forbidden: a reply inherits the thread visibility. Reply text is capped at 2000 characters against 5000 for a top-level post. Pass an `Idempotency-Key` header so a retry cannot double-post. `media_ids` is validated but not yet usable: the media endpoints are not part of this release, so any id supplied is rejected with 422 `MEDIA_NOT_FOUND`. Post without media until they ship.",
        "security": [
          {
            "oauth2": [
              "posts.write"
            ]
          },
          {
            "personalAccessToken": [
              "posts.write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Send the same key to retry a create safely: the first response is stored and replayed verbatim with `Idempotency-Replayed: true`. Reusing a key with a different body is 422; a duplicate still in flight is 409.",
            "schema": {
              "type": "string",
              "pattern": "^[!-~]{1,255}$",
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicCreatePostRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicPostDto"
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "`VALIDATION_FAILED`, `INVALID_IDEMPOTENCY_KEY`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "401": {
            "description": "`UNAUTHENTICATED`, `TOKEN_EXPIRED`, `TOKEN_REVOKED`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "403": {
            "description": "`INSUFFICIENT_SCOPE`, `TOKEN_NOT_PERMITTED`, `ACCOUNT_SUSPENDED`, `ONBOARDING_REQUIRED`, `CLIENT_DISABLED`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "404": {
            "description": "`NOT_FOUND`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "409": {
            "description": "`IDEMPOTENCY_IN_FLIGHT`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "422": {
            "description": "`IDEMPOTENCY_BODY_MISMATCH`, `AUDIENCE_UNAVAILABLE`, `MEDIA_NOT_FOUND`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "429": {
            "description": "`RATE_LIMITED`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "500": {
            "description": "`INTERNAL`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          }
        }
      }
    },
    "/public/v1/posts/{id}": {
      "get": {
        "operationId": "Posts_get",
        "tags": [
          "posts"
        ],
        "summary": "Get a post",
        "description": "Reads a single post by id, visibility-checked for the authorizing user. A post that exists but is not visible to them is a 404, identical to one that never existed — the API does not confirm the existence of content you cannot read.",
        "security": [
          {
            "oauth2": [
              "posts.read"
            ]
          },
          {
            "personalAccessToken": [
              "posts.read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicPostDto"
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "`UNAUTHENTICATED`, `TOKEN_EXPIRED`, `TOKEN_REVOKED`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "403": {
            "description": "`INSUFFICIENT_SCOPE`, `TOKEN_NOT_PERMITTED`, `ACCOUNT_SUSPENDED`, `ONBOARDING_REQUIRED`, `CLIENT_DISABLED`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "404": {
            "description": "`NOT_FOUND`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "429": {
            "description": "`RATE_LIMITED`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "500": {
            "description": "`INTERNAL`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "Posts_remove",
        "tags": [
          "posts"
        ],
        "summary": "Delete a post",
        "description": "Soft-deletes a post the authorizing user owns and returns 204 with no body. Deleting a post that belongs to someone else is a 403; a second delete of the same post is a 404, so it is not idempotent — treat the 404 as \"already gone\" rather than retrying.",
        "security": [
          {
            "oauth2": [
              "posts.write"
            ]
          },
          {
            "personalAccessToken": [
              "posts.write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted. No content.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "`UNAUTHENTICATED`, `TOKEN_EXPIRED`, `TOKEN_REVOKED`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "403": {
            "description": "`INSUFFICIENT_SCOPE`, `TOKEN_NOT_PERMITTED`, `FORBIDDEN`, `ACCOUNT_SUSPENDED`, `ONBOARDING_REQUIRED`, `CLIENT_DISABLED`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "404": {
            "description": "`NOT_FOUND`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "429": {
            "description": "`RATE_LIMITED`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "500": {
            "description": "`INTERNAL`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          }
        }
      }
    },
    "/public/v1/posts/{id}/replies": {
      "get": {
        "operationId": "Posts_replies",
        "tags": [
          "posts"
        ],
        "summary": "List replies to a post",
        "description": "Newest first, cursor-paginated. The parent post must be visible to the authorizing user: an unreadable thread is a 404 here exactly as it is on the post itself, so a hidden thread never leaks through its reply list.",
        "security": [
          {
            "oauth2": [
              "posts.read"
            ]
          },
          {
            "personalAccessToken": [
              "posts.read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 512
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicPostListDto"
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "`INVALID_CURSOR`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "401": {
            "description": "`UNAUTHENTICATED`, `TOKEN_EXPIRED`, `TOKEN_REVOKED`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "403": {
            "description": "`INSUFFICIENT_SCOPE`, `TOKEN_NOT_PERMITTED`, `ACCOUNT_SUSPENDED`, `ONBOARDING_REQUIRED`, `CLIENT_DISABLED`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "404": {
            "description": "`NOT_FOUND`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "429": {
            "description": "`RATE_LIMITED`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "500": {
            "description": "`INTERNAL`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          }
        }
      }
    },
    "/public/v1/timeline": {
      "get": {
        "operationId": "Timeline_timeline",
        "tags": [
          "timeline"
        ],
        "summary": "Read the authorizing user's timeline",
        "description": "`scope=discovery` (the default) returns the public discovery surface; `scope=following` returns posts from accounts the user follows. Newest first, cursor-paginated, every row visibility-checked for the authorizing user. Community posts and reposts are not part of this surface in v1.",
        "security": [
          {
            "oauth2": [
              "posts.read"
            ]
          },
          {
            "personalAccessToken": [
              "posts.read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 20
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 512
            }
          },
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "discovery",
                "following"
              ],
              "default": "discovery"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicPostListDto"
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "`INVALID_CURSOR`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "401": {
            "description": "`UNAUTHENTICATED`, `TOKEN_EXPIRED`, `TOKEN_REVOKED`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "403": {
            "description": "`INSUFFICIENT_SCOPE`, `TOKEN_NOT_PERMITTED`, `ACCOUNT_SUSPENDED`, `ONBOARDING_REQUIRED`, `CLIENT_DISABLED`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "404": {
            "description": "`NOT_FOUND`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "429": {
            "description": "`RATE_LIMITED`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          },
          "500": {
            "description": "`INTERNAL`",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this request in Mouve's logs — quote it in any support report. It is also the `request_id` member of every problem document.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProblemDto"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "oauth2": {
        "type": "oauth2",
        "description": "OAuth 2.1 authorization code with PKCE (S256 required). Access tokens live one hour; refresh tokens rotate on every use.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://api.mouve.it/oauth/authorize",
            "tokenUrl": "https://api.mouve.it/oauth/token",
            "refreshUrl": "https://api.mouve.it/oauth/token",
            "scopes": {
              "profile.read": "Read the profile of the authorizing user and the public, viewer-projected profiles of other users: username, display name, bio, avatar, and follower counts. Never includes body measurements — those are behind body.read.",
              "profile.write": "Update the authorizing user’s public profile fields: display name, bio, location, website, and avatar. Identity fields (username, date of birth, sex) and body measurements are excluded — body metrics require body.write.",
              "body.read": "Read the authorizing user’s body measurements: height, current weight, body-fat percentage, and their history. Health data (GDPR Article 9) — requires explicit consent.",
              "body.write": "Record body measurements (height, weight, body-fat percentage) on the authorizing user’s behalf. Health data (GDPR Article 9) — requires explicit consent.",
              "follows.write": "Follow and unfollow other users on the authorizing user’s behalf (social-graph mutations).",
              "posts.read": "Read the authorizing user’s feed and any posts, replies, likes, and attached media their account can see. Visibility is always viewer-projected — the token sees no more than the user would.",
              "posts.write": "Create posts and replies (including media upload/attach), like and comment, and delete the authorizing user’s own posts, acting as that user. Agent-authored content is provenance-stamped.",
              "workouts.read": "Read the authorizing user’s workouts, training sessions and history, and search the exercise library. Health data (GDPR Article 9) — requires explicit consent.",
              "workouts.write": "Create workout templates and log completed training sessions on the authorizing user’s behalf. Health data (GDPR Article 9) — requires explicit consent.",
              "nutrition.read": "Read the authorizing user’s nutrition data and use food search, food-description parsing, AI food-image analysis, and meal-plan generation. Health data (GDPR Article 9) — requires explicit consent.",
              "nutrition.write": "Log meals and nutrition diary entries on the authorizing user’s behalf. Health data (GDPR Article 9) — requires explicit consent.",
              "notifications.read": "Read the authorizing user’s notifications and recent account activity (new likes, comments, follows) so a connected assistant can react to it.",
              "memory.read": "Read the per-user memory notes connected assistants have stored for conversational continuity.",
              "memory.write": "Store per-user memory notes for conversational continuity across sessions of connected assistants.",
              "world.read": "Search the World map of outdoor gyms around coordinates you give the assistant and estimate walking, running and cycling routes (time, distance, calories, steps). Positions are used for the request only and are not stored.",
              "world.write": "Save a planned route (start position, destination and mode) to the authorizing user’s account so it appears in the Mouve app as \"Planned by your assistant\". The start position is personal location data and is deleted after 7 days."
            }
          }
        }
      },
      "personalAccessToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "A personal access token (`mouve_pat_` + 41 characters) sent as `Authorization: Bearer`. It carries a fixed scope set chosen when the token was created and acts only for the account that made it."
      }
    },
    "schemas": {
      "PublicProblemDto": {
        "type": "object",
        "description": "RFC 9457 `application/problem+json` body every `/public/v1` error uses. `type` is a stable URI per code (`https://developers.mouve.it/errors/<kebab>`), `code` is the machine contract, `request_id` correlates with API logs. `errors` is present on VALIDATION_FAILED only.",
        "properties": {
          "type": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "number"
          },
          "code": {
            "type": "string",
            "enum": [
              "VALIDATION_FAILED",
              "INVALID_CURSOR",
              "INVALID_IDEMPOTENCY_KEY",
              "UNAUTHENTICATED",
              "TOKEN_EXPIRED",
              "TOKEN_REVOKED",
              "INSUFFICIENT_SCOPE",
              "VENUE_NOT_VERIFIED",
              "APPROVAL_REQUIRED",
              "TOKEN_NOT_PERMITTED",
              "FORBIDDEN",
              "ACCOUNT_SUSPENDED",
              "ONBOARDING_REQUIRED",
              "CLIENT_DISABLED",
              "NOT_FOUND",
              "IDEMPOTENCY_IN_FLIGHT",
              "SLOT_UNAVAILABLE",
              "CHANGESET_STALE",
              "CHANGESET_OPEN",
              "CONFLICT",
              "ENDPOINT_RETIRED",
              "PAYLOAD_TOO_LARGE",
              "IDEMPOTENCY_BODY_MISMATCH",
              "OUTSIDE_HOURS",
              "AUDIENCE_UNAVAILABLE",
              "MEDIA_NOT_FOUND",
              "MEDIA_ALREADY_ATTACHED",
              "MEDIA_URL_INVALID",
              "MEDIA_URL_BLOCKED",
              "MEDIA_FETCH_FAILED",
              "MEDIA_TOO_LARGE",
              "MEDIA_UNSUPPORTED_TYPE",
              "MEDIA_VIDEO_UNSUPPORTED",
              "RATE_LIMITED",
              "USAGE_LIMIT_EXCEEDED",
              "MEDIA_QUOTA_EXCEEDED",
              "INTERNAL",
              "UNAVAILABLE",
              "VENUE_AGENT_WRITES_DISABLED"
            ]
          },
          "detail": {
            "type": "string"
          },
          "request_id": {
            "type": "string"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "field": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              },
              "required": [
                "field",
                "message"
              ],
              "additionalProperties": false
            }
          },
          "retry_after_seconds": {
            "type": "number",
            "description": "Extension members present on 429 responses only."
          },
          "scope": {
            "type": "string"
          },
          "window": {
            "type": "string"
          },
          "limit": {
            "type": "number"
          },
          "fix": {
            "type": "string",
            "description": "Agent-readable remedy (\"send `starts_at` on a 30-minute boundary\"). Optional on every code; the venue codes always carry one."
          },
          "docs_url": {
            "type": "string",
            "description": "Link to the docs for this code."
          },
          "approval_url": {
            "type": "string",
            "description": "APPROVAL_REQUIRED only: where the owner approves. Not retried."
          }
        },
        "required": [
          "type",
          "title",
          "status",
          "code",
          "detail",
          "request_id"
        ],
        "additionalProperties": false
      },
      "PublicMeDto": {
        "type": "object",
        "description": "`GET /public/v1/me` — the authorizing user's own public identity.",
        "properties": {
          "id": {
            "type": "string"
          },
          "username": {
            "type": "string",
            "description": "Handle without the leading `@`, as it appears in a permalink."
          },
          "display_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "avatar_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "bio": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "description": "ISO 8601 timestamp in UTC."
          },
          "verified": {
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "username",
          "display_name",
          "avatar_url",
          "bio",
          "created_at",
          "verified"
        ],
        "additionalProperties": false
      },
      "PublicUserDto": {
        "type": "object",
        "description": "`GET /public/v1/users/:username` — viewer-projected. No email, date of birth, sex, height, or weight is ever returned, under any scope.",
        "properties": {
          "id": {
            "type": "string"
          },
          "username": {
            "type": "string",
            "description": "Handle without the leading `@`, as it appears in a permalink."
          },
          "display_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "avatar_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "bio": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "description": "ISO 8601 timestamp in UTC."
          },
          "verified": {
            "type": "boolean"
          },
          "private": {
            "type": "boolean",
            "description": "The account gates its content behind follow approval."
          }
        },
        "required": [
          "id",
          "username",
          "display_name",
          "avatar_url",
          "bio",
          "created_at",
          "verified",
          "private"
        ],
        "additionalProperties": false
      },
      "PublicUserRefDto": {
        "type": "object",
        "description": "Embedded author reference on a post.",
        "properties": {
          "id": {
            "type": "string"
          },
          "username": {
            "type": "string"
          },
          "display_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "avatar_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "verified": {
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "username",
          "display_name",
          "avatar_url",
          "verified"
        ],
        "additionalProperties": false
      },
      "PublicMediaRefDto": {
        "type": "object",
        "description": "Media attached to a post. `id` is null for media uploaded through the Mouve apps rather than the API; API-uploaded media carry a real id once the media endpoints ship. `width`, `height`, and `content_type` are null when unknown, and may become non-null later — a nullable field gaining a value is not a breaking change.",
        "properties": {
          "id": {
            "type": [
              "string",
              "null"
            ]
          },
          "url": {
            "type": "string"
          },
          "width": {
            "type": [
              "number",
              "null"
            ]
          },
          "height": {
            "type": [
              "number",
              "null"
            ]
          },
          "content_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "alt": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "url",
          "width",
          "height",
          "content_type",
          "alt"
        ],
        "additionalProperties": false
      },
      "PublicPostDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "text": {
            "type": "string"
          },
          "author": {
            "$ref": "#/components/schemas/PublicUserRefDto"
          },
          "created_at": {
            "type": "string",
            "description": "ISO 8601 timestamp in UTC."
          },
          "audience": {
            "type": "string",
            "enum": [
              "everyone",
              "following",
              "only_me"
            ]
          },
          "in_reply_to_post_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The post this one replies to; null on a top-level post."
          },
          "media": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublicMediaRefDto"
            }
          },
          "metrics": {
            "type": "object",
            "properties": {
              "like_count": {
                "type": "number"
              },
              "reply_count": {
                "type": "number"
              },
              "repost_count": {
                "type": "number"
              },
              "view_count": {
                "type": "number"
              }
            },
            "required": [
              "like_count",
              "reply_count",
              "repost_count",
              "view_count"
            ],
            "additionalProperties": false
          },
          "permalink": {
            "type": "string",
            "description": "Absolute URL, computed server-side: `{APP_URL}/{username}/post/{id}`."
          },
          "app_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Provenance from `metadata.agent`; null = first-party."
          }
        },
        "required": [
          "id",
          "text",
          "author",
          "created_at",
          "audience",
          "in_reply_to_post_id",
          "media",
          "metrics",
          "permalink",
          "app_id"
        ],
        "additionalProperties": false
      },
      "PublicPostListDto": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublicPostDto"
            }
          },
          "meta": {
            "type": "object",
            "properties": {
              "next_cursor": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "next_cursor"
            ],
            "additionalProperties": false,
            "description": "`next_cursor` is null on the last page."
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false
      },
      "PublicCreatePostRequest": {
        "type": "object",
        "properties": {
          "text": {
            "type": "string",
            "minLength": 1,
            "maxLength": 5000
          },
          "audience": {
            "type": "string",
            "enum": [
              "everyone",
              "following",
              "only_me"
            ]
          },
          "media_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "maxItems": 5
          },
          "in_reply_to_post_id": {
            "type": "string",
            "format": "uuid"
          }
        },
        "required": [
          "text"
        ],
        "additionalProperties": false
      }
    }
  }
}
