{
  "openapi": "3.1.0",
  "info": {
    "title": "Flow Relay API",
    "version": "1.0.0",
    "description": "Project-scoped REST API for context handoffs and AI insights. The MCP server and editor extension are clients of this same API.",
    "contact": {
      "name": "Flow Relay",
      "url": "https://www.flowrelay.it/docs"
    }
  },
  "servers": [
    {
      "url": "https://www.flowrelay.it/api/v1"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Projects"
    },
    {
      "name": "Handoffs"
    },
    {
      "name": "Insights"
    },
    {
      "name": "Events"
    },
    {
      "name": "Integrations"
    },
    {
      "name": "Jobs"
    },
    {
      "name": "Discord"
    }
  ],
  "paths": {
    "/projects": {
      "get": {
        "operationId": "list_projects",
        "summary": "List accessible projects",
        "description": "Returns the tenant context for the key owner: account type, organization memberships, and every personal or organization project the key can access (role-scoped).",
        "tags": [
          "Projects"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "example": {
                  "account_type": "business",
                  "organizations": [
                    {
                      "id": "org_3f2a",
                      "name": "Acme",
                      "slug": "acme",
                      "role": "admin",
                      "is_temporary_admin": false
                    }
                  ],
                  "projects": [
                    {
                      "id": "proj_8c1d",
                      "name": "Payments",
                      "slug": "payments",
                      "description": "Billing and checkout",
                      "organization_id": "org_3f2a",
                      "organization_name": "Acme",
                      "organization_slug": "acme",
                      "project_type": "organization",
                      "access_role": "admin",
                      "created_at": "2026-05-01T09:12:00Z",
                      "updated_at": "2026-06-20T14:03:00Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "429": {
            "description": "Rate limit exceeded (60/min/key)."
          }
        }
      }
    },
    "/handoffs": {
      "get": {
        "operationId": "list_handoffs",
        "summary": "List handoffs",
        "description": "Lists handoffs across accessible projects, newest first. Without project_id it spans every project the key can access.",
        "tags": [
          "Handoffs"
        ],
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter by status.",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "archived",
                "all"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum rows to return (1–100).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "project_id",
            "in": "query",
            "required": false,
            "description": "Restrict to a single project.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "example": {
                  "handoffs": [
                    {
                      "id": "ho_1a2b",
                      "project_id": "proj_8c1d",
                      "title": "Checkout refactor handoff",
                      "summary": "Migrated the checkout flow to the new pricing engine.",
                      "status": "active",
                      "sources": [
                        "github",
                        "linear"
                      ],
                      "key_changes": [
                        "Replaced legacy tax calc"
                      ],
                      "decisions": [
                        "Use flat per-artifact pricing"
                      ],
                      "open_questions": [
                        "Backfill historical invoices?"
                      ],
                      "next_steps": [
                        "Wire the new webhook"
                      ],
                      "related_event_ids": [
                        "ev_44ff"
                      ],
                      "scope_type": "project",
                      "project_name": "Payments",
                      "created_at": "2026-06-20T14:03:00Z",
                      "updated_at": "2026-06-20T14:03:00Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "404": {
            "description": "Project not found or not accessible by this key."
          }
        }
      },
      "post": {
        "operationId": "create_handoff",
        "summary": "Generate a handoff",
        "description": "Enqueues a project handoff generation and returns a job id. Poll GET /jobs/{jobId} until status is completed. When sources/filters are omitted the project saved scope preferences are used.",
        "tags": [
          "Handoffs"
        ],
        "parameters": [],
        "responses": {
          "202": {
            "description": "Accepted – job enqueued.",
            "content": {
              "application/json": {
                "example": {
                  "jobId": "job_9f3c",
                  "status": "pending"
                }
              }
            }
          },
          "400": {
            "description": "project_id is missing."
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "404": {
            "description": "Project not found or not accessible by this key."
          },
          "429": {
            "description": "Rate limit exceeded (60/min/key)."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "project_id": {
                    "type": "string",
                    "description": "Project to generate the handoff for."
                  },
                  "sources": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Restrict generation to these sources (e.g. [\"github\",\"slack\"])."
                  },
                  "filters": {
                    "$ref": "#/components/schemas/SourceFilter",
                    "description": "Per-source projects / eventTypes / branches / priorities (AND-combined)."
                  }
                },
                "required": [
                  "project_id"
                ]
              },
              "example": {
                "project_id": "proj_8c1d",
                "sources": [
                  "github",
                  "linear"
                ],
                "filters": {
                  "github": {
                    "branches": [
                      "main"
                    ]
                  }
                }
              }
            }
          }
        }
      }
    },
    "/handoffs/filters": {
      "get": {
        "operationId": "handoff_filters",
        "summary": "List selectable filter options",
        "description": "Returns the real selectable filter values per connected source for a project: resources (repos / channels / boards), branches, event types, and priorities. Use these to build a valid SourceFilter instead of guessing.",
        "tags": [
          "Handoffs"
        ],
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "required": true,
            "description": "Project to resolve filter options for.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "example": {
                  "filters": {
                    "github": {
                      "projects": [
                        {
                          "id": "acme/payments",
                          "label": "acme/payments"
                        }
                      ],
                      "eventTypes": [
                        {
                          "value": "push",
                          "label": "Push"
                        }
                      ],
                      "priorities": [],
                      "branches": [
                        {
                          "value": "main",
                          "label": "main"
                        }
                      ]
                    },
                    "linear": {
                      "projects": [
                        {
                          "id": "team_x",
                          "label": "Core"
                        }
                      ],
                      "eventTypes": [
                        {
                          "value": "issue",
                          "label": "Issue"
                        }
                      ],
                      "priorities": [
                        {
                          "value": "high",
                          "label": "High"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "project_id is missing."
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "404": {
            "description": "Project not found or not accessible by this key."
          }
        }
      }
    },
    "/projects/{id}/insights": {
      "get": {
        "operationId": "list_insights",
        "summary": "List project insights",
        "description": "Lists AI insights for a project, newest first. Optionally filter by kind and status.",
        "tags": [
          "Insights"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Project id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "kind",
            "in": "query",
            "required": false,
            "description": "Filter by insight kind.",
            "schema": {
              "type": "string",
              "enum": [
                "cross_source_correlation",
                "onboarding_brief",
                "architecture_insight"
              ]
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter by status.",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "archived",
                "all"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum rows to return (1–100).",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "example": {
                  "insights": [
                    {
                      "id": "ins_7b2e",
                      "project_id": "proj_8c1d",
                      "kind": "architecture_insight",
                      "title": "Pricing engine trade-offs",
                      "summary": "Flat per-artifact pricing simplifies billing at the cost of margin variance.",
                      "data": {
                        "tradeOffs": [],
                        "risks": [],
                        "patterns": [],
                        "recommendations": []
                      },
                      "model_used": "gemini-2.5-flash",
                      "status": "active",
                      "related_event_ids": [
                        "ev_44ff"
                      ],
                      "created_at": "2026-06-21T08:00:00Z",
                      "updated_at": "2026-06-21T08:00:00Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "404": {
            "description": "Project not found or not accessible by this key."
          }
        }
      }
    },
    "/projects/{id}/insights/{kind}": {
      "post": {
        "operationId": "create_insight",
        "summary": "Generate an insight",
        "description": "Enqueues an insight generation for the project and returns a job id. The accepted body fields depend on kind. Poll GET /jobs/{jobId} for the result.",
        "tags": [
          "Insights"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Project id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "kind",
            "in": "path",
            "required": true,
            "description": "Insight kind.",
            "schema": {
              "type": "string",
              "enum": [
                "correlation",
                "onboarding",
                "architecture"
              ]
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Accepted – job enqueued.",
            "content": {
              "application/json": {
                "example": {
                  "jobId": "job_2d4a",
                  "status": "pending"
                }
              }
            }
          },
          "400": {
            "description": "Unsupported insight kind."
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "404": {
            "description": "Project not found or not accessible by this key."
          },
          "429": {
            "description": "Rate limit exceeded (60/min/key)."
          }
        },
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "lookbackHours": {
                    "type": "integer",
                    "description": "correlation only – window in hours (default 168)."
                  },
                  "lookbackDays": {
                    "type": "integer",
                    "description": "onboarding / architecture – window in days (30 / 14 default)."
                  },
                  "newMemberRole": {
                    "type": "string",
                    "description": "onboarding only – role of the person being onboarded."
                  },
                  "focusArea": {
                    "type": "string",
                    "description": "onboarding only – area to emphasise."
                  },
                  "focusQuestion": {
                    "type": "string",
                    "description": "architecture only – the question to investigate."
                  },
                  "sources": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Restrict to these sources."
                  },
                  "filters": {
                    "$ref": "#/components/schemas/SourceFilter",
                    "description": "Per-source filters (AND-combined)."
                  },
                  "maxEvents": {
                    "type": "integer",
                    "description": "Cap the events fed to the model."
                  }
                }
              },
              "example": {
                "focusQuestion": "Is the pricing engine coupling billing to providers?",
                "lookbackDays": 14
              }
            }
          }
        }
      }
    },
    "/events": {
      "get": {
        "operationId": "list_events",
        "summary": "List context events",
        "description": "Lists recent context events, newest first. With project_id it spans the project members and scoped sources; without it returns the key owner personal-stream events.",
        "tags": [
          "Events"
        ],
        "parameters": [
          {
            "name": "source",
            "in": "query",
            "required": false,
            "description": "Filter by source (e.g. github, slack, linear).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum rows to return (1–200).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "project_id",
            "in": "query",
            "required": false,
            "description": "Restrict to a project scope.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "example": {
                  "events": [
                    {
                      "id": "ev_44ff",
                      "user_id": "usr_1",
                      "source": "github",
                      "event_type": "push",
                      "title": "Push to acme/payments",
                      "content": "3 commits on main",
                      "created_at": "2026-06-20T13:55:00Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "404": {
            "description": "Project not found or not accessible by this key."
          }
        }
      }
    },
    "/integrations": {
      "get": {
        "operationId": "list_integrations",
        "summary": "List integrations",
        "description": "Lists connected integrations. With project_id it returns the project-scoped resources with health and provider coverage; without it returns the key owner connected sources.",
        "tags": [
          "Integrations"
        ],
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "required": false,
            "description": "Return project-scoped integrations instead of personal ones.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "example": {
                  "integrations": [
                    {
                      "source": "github",
                      "workspace_id": "acme",
                      "workspace_name": "Acme",
                      "connected_at": "2026-05-01T09:00:00Z",
                      "scope": "project",
                      "resource_type": "repository",
                      "connection_status": "active",
                      "providers_connected": 2,
                      "last_validated_at": "2026-06-20T10:00:00Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "404": {
            "description": "Project not found or not accessible by this key."
          }
        }
      }
    },
    "/integrations/untracked": {
      "get": {
        "operationId": "untracked_integrations",
        "summary": "List untracked resources",
        "description": "Returns active resources (last 30 days) that produced events but are not yet assigned to any project. The response is a bare array, sorted by most recent activity.",
        "tags": [
          "Integrations"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "example": [
                  {
                    "source": "slack",
                    "resource_id": "C0123",
                    "resource_name": "#incidents",
                    "resource_type": "channel",
                    "event_count": 42,
                    "last_event_at": "2026-06-20T12:00:00Z"
                  }
                ]
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "500": {
            "description": "Discovery failed for a connected source."
          }
        }
      }
    },
    "/jobs/{jobId}": {
      "get": {
        "operationId": "get_job",
        "summary": "Poll an async job",
        "description": "Returns the job record and, once completed, the generated artifact (handoff or insight). Poll this after any 202 response until status is completed or failed.",
        "tags": [
          "Jobs"
        ],
        "parameters": [
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "description": "Job id from a 202 response.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "example": {
                  "job": {
                    "id": "job_9f3c",
                    "project_id": "proj_8c1d",
                    "kind": "project_handoff",
                    "status": "completed",
                    "result_kind": "handoff",
                    "result_id": "ho_1a2b",
                    "error": null,
                    "error_code": null,
                    "created_at": "2026-06-20T14:02:00Z",
                    "updated_at": "2026-06-20T14:03:00Z"
                  },
                  "result": {
                    "id": "ho_1a2b",
                    "title": "Checkout refactor handoff"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "404": {
            "description": "Job not found or not accessible."
          }
        }
      }
    },
    "/discord/channels": {
      "get": {
        "operationId": "discord_channels",
        "summary": "List Discord channels",
        "description": "Lists the text channels in the Discord guild connected to the key owner.",
        "tags": [
          "Discord"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "example": {
                  "channels": [
                    {
                      "id": "987654",
                      "name": "general",
                      "type": 0
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "No guild associated with this integration."
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "404": {
            "description": "Discord not connected."
          }
        }
      }
    },
    "/discord/send": {
      "post": {
        "operationId": "discord_send",
        "summary": "Send a Discord message",
        "description": "Sends a message to a channel in the connected guild. The channel must belong to that guild (cross-tenant posts are rejected).",
        "tags": [
          "Discord"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "example": {
                  "ok": true,
                  "message_id": "111222333"
                }
              }
            }
          },
          "400": {
            "description": "channel_id and content are required."
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "403": {
            "description": "Channel does not belong to the connected guild."
          },
          "404": {
            "description": "Discord not connected."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "channel_id": {
                    "type": "string",
                    "description": "Target channel id."
                  },
                  "content": {
                    "type": "string",
                    "description": "Message text."
                  }
                },
                "required": [
                  "channel_id",
                  "content"
                ]
              },
              "example": {
                "channel_id": "987654",
                "content": "Handoff ready"
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Flow Relay API key, prefixed with fr_."
      }
    },
    "schemas": {
      "SourceFilter": {
        "type": "object",
        "description": "Per-source filters, AND-combined. Keyed by source id.",
        "additionalProperties": {
          "type": "object",
          "properties": {
            "projects": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "branches": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "eventTypes": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "priorities": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          }
        }
      }
    }
  }
}
