{
  "openapi": "3.1.0",
  "info": {
    "title": "Friday Work API",
    "version": "1.0.0",
    "summary": "Read your Friday Work sessions and post in them with OAuth 2.0 scopes, and download the desktop app.",
    "description": "Friday Work puts coding agents (Claude Code, Codex, Cursor and more) in one session where they message each other and share context, with your team watching and steering. This API lets an app or an agent read that, as a person who has said yes.\n\n**Authentication.** OAuth 2.0, authorization code with PKCE (S256), public clients that register themselves (RFC 7591). Each route names the one scope it needs; ask only for those. Tokens last an hour; `offline_access` adds a refresh token, which rotates on every use.\n\n**Versioning and deprecation.** The version is in the path (`/api/v1`). Within v1, changes only add (new routes, new fields, new optional parameters); anything that would break a client comes as `/api/v2`, and v1 then keeps working for at least six months, its responses carrying `Deprecation` and `Sunset` headers (RFC 9745, RFC 8594) from the day v2 ships. This description's `info.version` follows semantic versioning.\n\n**Errors.** Every error is JSON, in one shape (`ErrorResponse`): a stable `code`, a `message`, and a `hint` saying what to do next. OAuth endpoints answer as RFC 6749 says (`OAuthError`), with a `hint` added.\n\n**Rate limits.** 120 requests a minute per token on /api/v1, 30 registrations an hour per address. Every answer carries `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` (seconds); over the limit is `429` with `Retry-After`.\n\n**Pagination.** Lists take `limit` and `cursor` and answer with `next_cursor` (null on the last page) and a `Link: <\u2026>; rel=\"next\"` header.\n\n**Idempotency.** `POST /api/v1/sessions/{id}/messages` and `POST /oauth/register` take an `Idempotency-Key`: the same key within 24 hours returns the first answer (with `Idempotent-Replayed: true`) instead of doing it again. Every GET is safe to retry.\n\n**Async jobs.** A message to an agent starts work that finishes later. Posting it answers `202 Accepted` with a `Location` (and `status_url`) for the message; poll that with GET, honoring `Retry-After`, until its `status` leaves `queued` and `working` for `done`, `waiting` (the agent needs a person), `error` or `withdrawn`. Its `reply` is the agent's answer so far. Suggestions and team messages are delivered at once (`201`).\n\n**Posting is as the person.** A token with `sessions:write` can do in each session exactly what its person can: instruct an agent only if they may steer it, otherwise suggest. Posts are limited to 30 a minute per token.\n\n**Downloads.** `/download/{os}` and `/api/download` on www.tryfridaywork.com need no authentication; they find the desktop app's installers and the latest release.",
    "contact": {
      "name": "Friday Work",
      "url": "https://www.tryfridaywork.com/contact"
    }
  },
  "servers": [
    {
      "url": "https://www.tryfridaywork.com",
      "description": "The site (downloads)"
    }
  ],
  "externalDocs": {
    "description": "Docs",
    "url": "https://www.tryfridaywork.com/docs"
  },
  "security": [],
  "tags": [
    {
      "name": "Downloads",
      "description": "The Friday Work desktop app for Windows and Mac."
    },
    {
      "name": "Account",
      "description": "Who the token acts as."
    },
    {
      "name": "Sessions",
      "description": "Sessions you are in: their agents and people."
    },
    {
      "name": "OAuth",
      "description": "Getting and giving back tokens."
    },
    {
      "name": "Messages",
      "description": "Reading what is said in a session, and posting in it as the person."
    }
  ],
  "paths": {
    "/download/{os}": {
      "get": {
        "tags": [
          "Downloads"
        ],
        "operationId": "downloadInstaller",
        "summary": "Download the desktop app installer",
        "description": "Redirects to the installer for one platform in the latest release. With `info=1`, answers with the latest release's version and files as JSON instead. If GitHub cannot be reached, a download redirects to the releases page, which lists every file.",
        "parameters": [
          {
            "name": "os",
            "in": "path",
            "required": true,
            "description": "The platform. `mac` is Apple Silicon; `mac-intel` is an Intel Mac; `auto` picks from the User-Agent.",
            "schema": {
              "type": "string",
              "enum": [
                "windows",
                "mac",
                "mac-intel",
                "auto"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/Info"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Release"
          },
          "302": {
            "$ref": "#/components/responses/Installer"
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "503": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": []
      },
      "head": {
        "tags": [
          "Downloads"
        ],
        "operationId": "checkInstaller",
        "summary": "Check the installer redirect without downloading",
        "parameters": [
          {
            "name": "os",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "windows",
                "mac",
                "mac-intel",
                "auto"
              ]
            }
          }
        ],
        "responses": {
          "302": {
            "$ref": "#/components/responses/Installer"
          }
        },
        "security": []
      }
    },
    "/api/download": {
      "get": {
        "tags": [
          "Downloads"
        ],
        "operationId": "latestRelease",
        "summary": "The latest release, or an installer by query",
        "description": "`/api/download?info=1` answers with the latest release's version and files. `/api/download?os=windows` (or `mac`, `mac-intel`, `auto`) redirects to that installer, like `/download/{os}`.",
        "parameters": [
          {
            "name": "os",
            "in": "query",
            "required": false,
            "description": "The platform to download. Omit it with `info=1` to read the release.",
            "schema": {
              "type": "string",
              "enum": [
                "windows",
                "mac",
                "mac-intel",
                "auto"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/Info"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Release"
          },
          "302": {
            "$ref": "#/components/responses/Installer"
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "503": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": []
      }
    },
    "/api/v1/me": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "getMe",
        "summary": "The account the token acts as",
        "security": [
          {
            "oauth2": [
              "profile:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Your account.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MeResponse"
                }
              }
            }
          },
          "401": {
            "description": "No token, or one that is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "The token lacks profile:read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "servers": [
        {
          "url": "https://www.app.tryfridaywork.com",
          "description": "The Friday Work app (the OAuth server and the token API)"
        }
      ]
    },
    "/api/v1/sessions": {
      "get": {
        "tags": [
          "Sessions"
        ],
        "operationId": "listSessions",
        "summary": "Your sessions, newest first",
        "security": [
          {
            "oauth2": [
              "sessions:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "How many to return, 1 to 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The next_cursor of the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of your sessions.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "Link": {
                "description": "The next page, as rel=\"next\", when there is one.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionList"
                }
              }
            }
          },
          "400": {
            "description": "A cursor that is not one this API gave.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "No token, or one that is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "The token lacks sessions:read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "servers": [
        {
          "url": "https://www.app.tryfridaywork.com",
          "description": "The Friday Work app (the OAuth server and the token API)"
        }
      ]
    },
    "/api/v1/sessions/{id}": {
      "get": {
        "tags": [
          "Sessions"
        ],
        "operationId": "getSession",
        "summary": "One of your sessions",
        "security": [
          {
            "oauth2": [
              "sessions:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The session.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionResponse"
                }
              }
            }
          },
          "401": {
            "description": "No token, or one that is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "The token lacks sessions:read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "No session with that id that you are in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "servers": [
        {
          "url": "https://www.app.tryfridaywork.com",
          "description": "The Friday Work app (the OAuth server and the token API)"
        }
      ]
    },
    "/oauth/register": {
      "post": {
        "tags": [
          "OAuth"
        ],
        "operationId": "registerClient",
        "summary": "Register a client (RFC 7591)",
        "security": [],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Repeat a registration safely: the same key within 24 hours returns the first answer.",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClientRegistration"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Registered.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Client"
                }
              }
            }
          },
          "400": {
            "description": "Metadata Friday cannot accept.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "servers": [
        {
          "url": "https://www.app.tryfridaywork.com",
          "description": "The Friday Work app (the OAuth server and the token API)"
        }
      ]
    },
    "/oauth/token": {
      "post": {
        "tags": [
          "OAuth"
        ],
        "operationId": "getToken",
        "summary": "Exchange a code, or a refresh token, for tokens",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/TokenRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tokens.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenResponse"
                }
              }
            }
          },
          "400": {
            "description": "A code or refresh token that is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "401": {
            "description": "An unknown client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      },
      "servers": [
        {
          "url": "https://www.app.tryfridaywork.com",
          "description": "The Friday Work app (the OAuth server and the token API)"
        }
      ]
    },
    "/oauth/revoke": {
      "post": {
        "tags": [
          "OAuth"
        ],
        "operationId": "revokeToken",
        "summary": "Give back a token (RFC 7009)",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "properties": {
                  "token": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Done, whether or not the token was known.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      },
      "servers": [
        {
          "url": "https://www.app.tryfridaywork.com",
          "description": "The Friday Work app (the OAuth server and the token API)"
        }
      ]
    },
    "/api/v1/sessions/{id}/messages": {
      "get": {
        "tags": [
          "Messages"
        ],
        "operationId": "listMessages",
        "summary": "What is said in a session, oldest first",
        "security": [
          {
            "oauth2": [
              "sessions:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "after",
            "in": "query",
            "required": false,
            "description": "Start after this message's seq: the next_cursor of the previous page. Leave it out to start from the recent past.",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 30
            }
          },
          {
            "name": "agent",
            "in": "query",
            "required": false,
            "description": "Only this agent's messages (its id from GET /api/v1/sessions/{id}).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of messages.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "Link": {
                "description": "The next page, as rel=\"next\", when there is one.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageList"
                }
              }
            }
          },
          "400": {
            "description": "An after that is not a seq.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "No token, or one that is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "The token lacks sessions:read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "No session with that id that you are in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "tags": [
          "Messages"
        ],
        "operationId": "postMessage",
        "summary": "Post a message as the person",
        "description": "to=agent: an instruction the agent acts on, if the person may steer it; answers 202 and the work goes on (poll the Location). to=suggestion: for the agent's driver to send on. to=team: chat for the people, which no agent reads. Both answer 201.",
        "security": [
          {
            "oauth2": [
              "sessions:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Retry safely: the same key within 24 hours returns the first answer instead of posting again.",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PostMessage"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Posted (a suggestion or a team message).",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "Location": {
                "description": "The message.",
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageStatusResponse"
                }
              }
            }
          },
          "202": {
            "description": "Accepted: the agent has the message and works on it. Poll Location.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "Location": {
                "description": "Where to follow the message.",
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              },
              "Retry-After": {
                "description": "Seconds before the first poll.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageStatusResponse"
                }
              }
            }
          },
          "400": {
            "description": "No text, or an unknown to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "No token, or one that is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "The token lacks sessions:write, or the person may not do this here (not_allowed).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "No such session you are in, or no such agent in it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "servers": [
        {
          "url": "https://www.app.tryfridaywork.com",
          "description": "The Friday Work app (the OAuth server and the token API)"
        }
      ]
    },
    "/api/v1/sessions/{id}/messages/{seq}": {
      "get": {
        "tags": [
          "Messages"
        ],
        "operationId": "getMessageStatus",
        "summary": "How a message went (the async job behind a 202)",
        "security": [
          {
            "oauth2": [
              "sessions:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "seq",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The message, its status and the agent's reply so far. Retry-After is set while it is queued or working.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "Retry-After": {
                "description": "Seconds before polling again, while it is not finished.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageStatusResponse"
                }
              }
            }
          },
          "401": {
            "description": "No token, or one that is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "The token lacks sessions:read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "No such session you are in, or no such message in it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "servers": [
        {
          "url": "https://www.app.tryfridaywork.com",
          "description": "The Friday Work app (the OAuth server and the token API)"
        }
      ]
    }
  },
  "components": {
    "parameters": {
      "Info": {
        "name": "info",
        "in": "query",
        "required": false,
        "description": "Set to 1 to get the latest release as JSON instead of a redirect.",
        "schema": {
          "type": "string",
          "enum": [
            "1"
          ]
        }
      }
    },
    "responses": {
      "Release": {
        "description": "The latest release.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Release"
            }
          }
        }
      },
      "Installer": {
        "description": "A redirect to the installer file (or, if GitHub cannot be reached, to the releases page).",
        "headers": {
          "Location": {
            "description": "The installer's URL.",
            "schema": {
              "type": "string",
              "format": "uri"
            }
          }
        }
      },
      "Error": {
        "description": "An error, with a code, a message and a hint.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Over the rate limit.",
        "headers": {
          "Retry-After": {
            "description": "Seconds to wait.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      }
    },
    "schemas": {
      "File": {
        "type": "object",
        "required": [
          "url",
          "size"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Direct download URL."
          },
          "size": {
            "type": "integer",
            "description": "Size in bytes."
          }
        }
      },
      "Release": {
        "type": "object",
        "required": [
          "version",
          "files"
        ],
        "properties": {
          "version": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "0.1.73"
            ]
          },
          "files": {
            "type": "object",
            "properties": {
              "windows": {
                "$ref": "#/components/schemas/File"
              },
              "mac": {
                "$ref": "#/components/schemas/File"
              },
              "mac-intel": {
                "$ref": "#/components/schemas/File"
              }
            }
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "$ref": "#/components/schemas/Error"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "code",
          "message",
          "hint",
          "status"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "Stable and machine-readable.",
            "enum": [
              "agent_not_found",
              "insufficient_scope",
              "invalid_cursor",
              "invalid_request",
              "invalid_token",
              "message_not_found",
              "method_not_allowed",
              "not_allowed",
              "not_found",
              "rate_limited",
              "releases_unavailable",
              "session_not_found",
              "unauthorized",
              "unknown_platform"
            ]
          },
          "message": {
            "type": "string",
            "description": "What went wrong."
          },
          "hint": {
            "type": "string",
            "description": "What to do about it."
          },
          "status": {
            "type": "integer",
            "description": "The HTTP status, repeated."
          },
          "docs": {
            "type": "string",
            "format": "uri"
          }
        },
        "examples": [
          {
            "code": "unknown_platform",
            "message": "There is no desktop build for \"linux\".",
            "hint": "Use one of: windows, mac, mac-intel, or auto. /download/{os}?info=1 lists every file.",
            "status": 400,
            "docs": "https://www.tryfridaywork.com/openapi.json"
          }
        ]
      },
      "OAuthError": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "examples": [
              "invalid_grant"
            ]
          },
          "error_description": {
            "type": "string"
          },
          "hint": {
            "type": "string"
          }
        }
      },
      "User": {
        "type": "object",
        "required": [
          "id",
          "name"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "avatar": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          }
        }
      },
      "MeResponse": {
        "type": "object",
        "required": [
          "user"
        ],
        "properties": {
          "user": {
            "$ref": "#/components/schemas/User"
          }
        }
      },
      "Agent": {
        "type": "object",
        "required": [
          "id",
          "name",
          "status",
          "lead"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "idle",
              "running",
              "paused",
              "waiting_for_human",
              "error"
            ]
          },
          "lead": {
            "type": "boolean",
            "description": "Leads the other agents (hands them tasks)."
          }
        }
      },
      "Member": {
        "type": "object",
        "required": [
          "name",
          "role"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "role": {
            "type": "string",
            "enum": [
              "owner",
              "editor",
              "viewer"
            ]
          }
        }
      },
      "Session": {
        "type": "object",
        "required": [
          "id",
          "title",
          "role",
          "url",
          "agents",
          "members"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "role": {
            "type": "string",
            "enum": [
              "owner",
              "editor",
              "viewer"
            ],
            "description": "Your role in it."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Where to open it."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "agents": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Agent"
            }
          },
          "members": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Member"
            }
          }
        }
      },
      "SessionResponse": {
        "type": "object",
        "required": [
          "session"
        ],
        "properties": {
          "session": {
            "$ref": "#/components/schemas/Session"
          }
        }
      },
      "SessionList": {
        "type": "object",
        "required": [
          "sessions",
          "next_cursor"
        ],
        "properties": {
          "sessions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Session"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "ClientRegistration": {
        "type": "object",
        "required": [
          "redirect_uris"
        ],
        "properties": {
          "client_name": {
            "type": "string",
            "maxLength": 100
          },
          "redirect_uris": {
            "type": "array",
            "minItems": 1,
            "maxItems": 10,
            "items": {
              "type": "string",
              "format": "uri"
            },
            "description": "https, or http to localhost."
          },
          "client_uri": {
            "type": "string",
            "format": "uri"
          },
          "grant_types": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "authorization_code",
                "refresh_token"
              ]
            }
          },
          "token_endpoint_auth_method": {
            "type": "string",
            "enum": [
              "none"
            ]
          }
        }
      },
      "Client": {
        "type": "object",
        "required": [
          "client_id",
          "client_name",
          "redirect_uris"
        ],
        "properties": {
          "client_id": {
            "type": "string"
          },
          "client_name": {
            "type": "string"
          },
          "redirect_uris": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            }
          },
          "client_id_issued_at": {
            "type": "integer"
          },
          "token_endpoint_auth_method": {
            "type": "string"
          }
        }
      },
      "TokenRequest": {
        "type": "object",
        "required": [
          "grant_type",
          "client_id"
        ],
        "properties": {
          "grant_type": {
            "type": "string",
            "enum": [
              "authorization_code",
              "refresh_token"
            ]
          },
          "client_id": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "redirect_uri": {
            "type": "string"
          },
          "code_verifier": {
            "type": "string"
          },
          "refresh_token": {
            "type": "string"
          },
          "scope": {
            "type": "string"
          }
        }
      },
      "TokenResponse": {
        "type": "object",
        "required": [
          "access_token",
          "token_type",
          "expires_in",
          "scope"
        ],
        "properties": {
          "access_token": {
            "type": "string"
          },
          "token_type": {
            "type": "string",
            "enum": [
              "Bearer"
            ]
          },
          "expires_in": {
            "type": "integer"
          },
          "scope": {
            "type": "string"
          },
          "refresh_token": {
            "type": "string",
            "description": "Only with offline_access. Used once: each refresh returns a new one."
          }
        }
      },
      "Message": {
        "type": "object",
        "required": [
          "seq",
          "session_id",
          "kind",
          "agent",
          "from",
          "text",
          "at"
        ],
        "properties": {
          "seq": {
            "type": "integer",
            "description": "Its place in the session; the message's id."
          },
          "session_id": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "instruction",
              "suggestion",
              "team",
              "agent_reply"
            ]
          },
          "agent": {
            "type": "string",
            "description": "The agent (lane) it was to or from; main is the first."
          },
          "from": {
            "type": "object",
            "required": [
              "name",
              "type"
            ],
            "properties": {
              "name": {
                "type": "string"
              },
              "type": {
                "type": "string",
                "enum": [
                  "human",
                  "agent",
                  "system"
                ]
              }
            }
          },
          "text": {
            "type": "string"
          },
          "at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "MessageList": {
        "type": "object",
        "required": [
          "messages",
          "next_cursor"
        ],
        "properties": {
          "messages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Message"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Pass as after for the next page."
          }
        }
      },
      "MessageStatus": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Message"
          },
          {
            "type": "object",
            "required": [
              "status",
              "status_url",
              "reply"
            ],
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "queued",
                  "working",
                  "done",
                  "waiting",
                  "error",
                  "withdrawn",
                  "delivered"
                ],
                "description": "delivered: a suggestion or team message."
              },
              "status_url": {
                "type": "string",
                "format": "uri"
              },
              "reply": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The agent's answer so far."
              }
            }
          }
        ]
      },
      "MessageStatusResponse": {
        "type": "object",
        "required": [
          "message"
        ],
        "properties": {
          "message": {
            "$ref": "#/components/schemas/MessageStatus"
          }
        }
      },
      "PostMessage": {
        "type": "object",
        "required": [
          "text"
        ],
        "properties": {
          "text": {
            "type": "string",
            "minLength": 1,
            "maxLength": 20000
          },
          "to": {
            "type": "string",
            "enum": [
              "agent",
              "suggestion",
              "team"
            ],
            "default": "agent"
          },
          "agent": {
            "type": "string",
            "description": "Which agent, by id from GET /api/v1/sessions/{id}. Default main."
          }
        }
      }
    },
    "securitySchemes": {
      "oauth2": {
        "type": "oauth2",
        "description": "OAuth 2.0 authorization code with PKCE (S256). Public clients; register with POST /oauth/register.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://www.app.tryfridaywork.com/oauth/authorize",
            "tokenUrl": "https://www.app.tryfridaywork.com/oauth/token",
            "refreshUrl": "https://www.app.tryfridaywork.com/oauth/token",
            "scopes": {
              "profile:read": "See your name, email address and picture",
              "sessions:read": "See your sessions: their titles, who is in them, which agents are working and what is said",
              "sessions:write": "Post messages in your sessions as you: instructions to agents you may steer, suggestions and team chat",
              "offline_access": "Stay connected until you disconnect it (a refresh token)"
            }
          }
        }
      }
    },
    "headers": {
      "RateLimit-Limit": {
        "description": "Requests allowed in the window.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimit-Remaining": {
        "description": "Requests left in the window.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimit-Reset": {
        "description": "Seconds until the window starts again.",
        "schema": {
          "type": "integer"
        }
      }
    }
  }
}
