{
  "openapi": "3.1.0",
  "info": {
    "title": "Kord",
    "version": "1.0.0",
    "summary": "Version control for engineering documents",
    "description": "Kord helps organizations compare files, track versions, and review changes across engineering documents.\n\nThis document covers the endpoints an agent can call. The five `/api/*` endpoints need no sign-in: they back the public diff playground at https://work.withkord.com/diff and are held to a per-client rate limit and a 25.0 MB file cap. Everything else under `/api/` is the web application's own back end, authenticated by a browser session, and is not an API.\n\nTo show a user the diff of two files they have, call `POST /api/diff/share` with both files and give them the `url` it returns (or open it in a browser). The page renders the comparison for anyone who opens it, with no sign-in.\n\nTo act on a user's Kord data - their organizations, projects, folders, files, review sessions, comments, issues and change requests - use the MCP server at https://mcp.withkord.com/mcp. It is a Model Context Protocol endpoint (Streamable HTTP), authorized with OAuth 2.1; the user signs in with their Google or Microsoft account and every tool runs with that user's permissions. See https://withkord.com/mcp.\n\nA readable index of this site is at https://work.withkord.com/llms.txt.",
    "termsOfService": "https://withkord.com/terms",
    "contact": {
      "name": "Kord support",
      "email": "support@withkord.com",
      "url": "https://withkord.com"
    }
  },
  "servers": [
    {
      "url": "https://work.withkord.com",
      "description": "The Kord application"
    }
  ],
  "externalDocs": {
    "description": "llms.txt - every page with a Markdown version",
    "url": "https://work.withkord.com/llms.txt"
  },
  "tags": [
    {
      "name": "Diff playground",
      "description": "Anonymous endpoints behind https://work.withkord.com/diff. Rate-limited per client; files up to 25.0 MB. Nothing is kept beyond a content-addressed cache that is swept - except the two files behind a diff link, kept until that link expires."
    },
    {
      "name": "MCP",
      "description": "The Model Context Protocol server. Use an MCP client, not raw HTTP."
    }
  ],
  "paths": {
    "/api/extract": {
      "post": {
        "tags": [
          "Diff playground"
        ],
        "operationId": "extractFile",
        "summary": "Extract the text of a file",
        "description": "Returns the text of one file - and, for spreadsheets, Rockwell PLC projects and FactoryTalk View displays, a structured model beside it. Accepts any format Kord versions (.3dm, .3mf, .acd, .asc, .bdf, .bmp, .brep, .brp, .cid, .cir, .cnc, .csv, .dexpi, .dgn, .doc, .docx, .drl, .dwg, .dxf, .fmu, .gbl, .gbo, .gbp, .gbr, .gbs, .gcode, .gif, .gko, .glb, .gltf, .gm1, .gtl, .gto, .gtp, .gts, .icd, .ifc, .ifcxml, .iges, .igs, .iid, .jpeg, .jpg, .json, .kicad_pcb, .kicad_sch, .l5x, .log, .md, .mdl, .mo, .mpf, .nas, .nc, .nc1, .net, .ngc, .nwc, .nwd, .nwf, .obj, .ork, .pdf, .plcopen, .png, .ppt, .pptx, .raw, .reqif, .reqifz, .rfa, .rvt, .sat, .scd, .sdf, .sdnf, .sed, .skp, .slx, .sp, .spice, .ssd, .step, .stl, .stp, .svg, .tap, .txt, .vsd, .vsdm, .vsdx, .webp, .x_t, .xln, .xls, .xlsm, .xlsx, .xml, .yaml, .yml); a format with no text extraction returns `supported: false` and a placeholder.",
        "requestBody": {
          "required": true,
          "description": "The file to extract.",
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The file. At most 25.0 MB; above roughly 4 MB the request body is refused at the edge, so upload through `/api/diff/upload-url` first and send the JSON form instead."
                  }
                }
              }
            },
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UploadRef"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The extraction.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExtractionResult"
                }
              }
            }
          },
          "400": {
            "description": "No file, or an unreadable request body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The `fileKey` names an upload that has expired (uploads live about a day) or never existed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "The file is over the 25.0 MB limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests from this client. `Retry-After` says how many seconds to wait.",
            "headers": {
              "Retry-After": {
                "description": "Seconds until the client may retry.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The file could not be extracted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/diff/upload-url": {
      "post": {
        "tags": [
          "Diff playground"
        ],
        "operationId": "createUploadUrl",
        "summary": "Get a signed URL to upload a file over ~4 MB",
        "description": "Mints a one-time signed URL for one file. PUT the bytes to `uploadUrl` with the returned `method`, then pass `key` as `fileKey` to the other endpoints. Uploads are swept after about a day. The name only picks the converter; its extension must be one Kord accepts.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "fileName",
                  "size"
                ],
                "properties": {
                  "fileName": {
                    "type": "string",
                    "description": "The file name, with its extension.",
                    "examples": [
                      "drawing.dxf"
                    ]
                  },
                  "size": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 26214400,
                    "description": "The file size in bytes."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Where to PUT the bytes.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "key",
                    "uploadUrl",
                    "method"
                  ],
                  "properties": {
                    "key": {
                      "type": "string",
                      "description": "The `fileKey` to send to the other endpoints."
                    },
                    "uploadUrl": {
                      "type": "string",
                      "format": "uri"
                    },
                    "method": {
                      "type": "string",
                      "description": "The HTTP method to upload with.",
                      "examples": [
                        "PUT"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid `fileName` / `size`, or an extension Kord does not accept.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "The file is over the 25.0 MB limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests from this client. `Retry-After` says how many seconds to wait.",
            "headers": {
              "Retry-After": {
                "description": "Seconds until the client may retry.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "The conversion could not run right now (a cold or busy converter). Worth one retry after `Retry-After`.",
            "headers": {
              "Retry-After": {
                "description": "Seconds until the client may retry.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/diff/preview": {
      "post": {
        "tags": [
          "Diff playground"
        ],
        "operationId": "renderPreviewPdf",
        "summary": "Render an Office file to PDF",
        "description": "Converts one Word (.docx, .doc), PowerPoint (.pptx, .ppt), Excel (.xlsx, .xlsm, .xls) or Visio (.vsdx, .vsdm, .vsd) file to a PDF with the same renderers the app uses. Results are cached by content, so repeating a file costs nothing; a real conversion is charged to a tighter rate limit.",
        "requestBody": {
          "required": true,
          "description": "The Office file to render.",
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The file. At most 25.0 MB; above roughly 4 MB the request body is refused at the edge, so upload through `/api/diff/upload-url` first and send the JSON form instead."
                  }
                }
              }
            },
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UploadRef"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The rendered PDF.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "No file, an unreadable body, or a format preview does not support.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The `fileKey` names an upload that has expired (uploads live about a day) or never existed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "The file is over the 25.0 MB limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests from this client. `Retry-After` says how many seconds to wait.",
            "headers": {
              "Retry-After": {
                "description": "Seconds until the client may retry.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "The conversion could not run right now (a cold or busy converter). Worth one retry after `Retry-After`.",
            "headers": {
              "Retry-After": {
                "description": "Seconds until the client may retry.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/diff/redline": {
      "post": {
        "tags": [
          "Diff playground"
        ],
        "operationId": "redlineDocx",
        "summary": "Redline two Word documents",
        "description": "Compares `original` (before) with `revised` (after) and returns the tracked-changes comparison rendered as a PDF. Both files must be Word documents. Results are cached by the pair of contents.",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "original",
                  "revised"
                ],
                "properties": {
                  "original": {
                    "type": "string",
                    "format": "binary",
                    "description": "The document before the change."
                  },
                  "revised": {
                    "type": "string",
                    "format": "binary",
                    "description": "The document after the change."
                  }
                }
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "originalKey",
                  "originalName",
                  "revisedKey",
                  "revisedName"
                ],
                "properties": {
                  "originalKey": {
                    "type": "string"
                  },
                  "originalName": {
                    "type": "string"
                  },
                  "revisedKey": {
                    "type": "string"
                  },
                  "revisedName": {
                    "type": "string"
                  }
                },
                "description": "Both sides as uploads from `/api/diff/upload-url`."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The rendered PDF.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "A side is missing, unreadable, or not a Word document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The `fileKey` names an upload that has expired (uploads live about a day) or never existed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "The file is over the 25.0 MB limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests from this client. `Retry-After` says how many seconds to wait.",
            "headers": {
              "Retry-After": {
                "description": "Seconds until the client may retry.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "The conversion could not run right now (a cold or busy converter). Worth one retry after `Retry-After`.",
            "headers": {
              "Retry-After": {
                "description": "Seconds until the client may retry.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/diff/share": {
      "post": {
        "tags": [
          "Diff playground"
        ],
        "operationId": "createDiffShare",
        "summary": "Create a link to the diff of two files",
        "description": "The one call an agent needs to show a person a diff. Send the file before the change and the file after it; `url` in the response is a https://work.withkord.com/d/<token> page that renders the comparison for anyone who opens it, with no sign-in. Both files must be of one kind (two spreadsheets, two PDFs, two Word documents, ...) and each at most 25.0 MB. The link expires after `ttlDays` (7 by default, 30 at most); the two files are kept only until then, and an anonymous link can be neither listed nor revoked before it expires. Two files over roughly 4 MB together do not fit one request body: upload each through `/api/diff/upload-url` first and send the JSON form.",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "before",
                  "after"
                ],
                "properties": {
                  "before": {
                    "type": "string",
                    "format": "binary",
                    "description": "The file before the change."
                  },
                  "after": {
                    "type": "string",
                    "format": "binary",
                    "description": "The file after the change."
                  },
                  "ttlDays": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 30,
                    "description": "Days until the link expires. Default 7; out-of-range values are clamped."
                  },
                  "title": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Optional name for the comparison. Defaults to the two file names."
                  }
                }
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "before",
                  "after"
                ],
                "properties": {
                  "before": {
                    "$ref": "#/components/schemas/UploadRef"
                  },
                  "after": {
                    "$ref": "#/components/schemas/UploadRef"
                  },
                  "ttlDays": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 30
                  },
                  "title": {
                    "type": "string",
                    "maxLength": 200
                  }
                },
                "description": "Both sides as uploads from `/api/diff/upload-url`."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The link, with what it compares.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DiffShareLink"
                }
              }
            }
          },
          "400": {
            "description": "A side is missing, the two files are of different kinds, a format the playground does not render, or an upload key that has expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "The file is over the 25.0 MB limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests from this client. `Retry-After` says how many seconds to wait.",
            "headers": {
              "Retry-After": {
                "description": "Seconds until the client may retry.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The link could not be created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Anonymous links are over their shared daily storage budget, or storage is unavailable. Send the user to the diff page, which stores nothing, or retry tomorrow.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/mcp": {
      "servers": [
        {
          "url": "https://mcp.withkord.com",
          "description": "MCP host"
        }
      ],
      "post": {
        "tags": [
          "MCP"
        ],
        "operationId": "mcp",
        "summary": "The Kord MCP server (Streamable HTTP)",
        "description": "JSON-RPC 2.0 messages of the Model Context Protocol. Connect with an MCP client rather than calling this directly: on the first request the server answers 401 with a `WWW-Authenticate` header whose `resource_metadata` points at `/.well-known/oauth-protected-resource`, the client registers itself with the authorization server (RFC 7591), the user signs in with Google or Microsoft in a browser, and the client presents the resulting bearer token here.\n\nTools: whoami, list_organizations, list_projects, list_folders, list_cloud_folders, list_folder_members, list_files, list_file_versions, read_file, list_review_sessions, list_my_review_sessions, get_review_session, get_review_session_diff, list_review_session_comments, list_annotations, list_issues, get_issue, list_change_requests, get_change_request, list_document_records, create_folder, create_review_session, update_review_session_files, add_review_session_comment, resolve_review_session_comment, assign_reviewers, create_issue, create_diff_share. The agent never writes file bytes; documents change through review sessions.",
        "security": [
          {
            "oauth2": [
              "openid",
              "email",
              "profile"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "A JSON-RPC 2.0 request or notification."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A JSON-RPC response, as JSON or as a server-sent event stream.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "No or invalid bearer token. `WWW-Authenticate` names the resource metadata URL.",
            "headers": {
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                },
                "description": "Bearer resource_metadata=\"<origin>/.well-known/oauth-protected-resource\""
              }
            }
          }
        }
      }
    },
    "/.well-known/oauth-protected-resource": {
      "get": {
        "tags": [
          "MCP"
        ],
        "operationId": "oauthProtectedResourceMetadata",
        "summary": "OAuth protected resource metadata (RFC 9728)",
        "description": "Names the authorization server, the scopes a client may request and where the documentation lives. Served with `Access-Control-Allow-Origin: *`.",
        "responses": {
          "200": {
            "description": "The metadata document.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "resource",
                    "authorization_servers"
                  ],
                  "properties": {
                    "resource": {
                      "type": "string",
                      "format": "uri"
                    },
                    "authorization_servers": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "format": "uri"
                      }
                    },
                    "scopes_supported": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "bearer_methods_supported": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "What went wrong, written for a person."
          },
          "code": {
            "type": "string",
            "description": "A stable slug for the failure, when there is one."
          },
          "retryable": {
            "type": "boolean",
            "description": "True when the same request may succeed shortly."
          }
        }
      },
      "UploadRef": {
        "type": "object",
        "required": [
          "fileKey",
          "fileName"
        ],
        "description": "A file already uploaded through `/api/diff/upload-url`.",
        "properties": {
          "fileKey": {
            "type": "string",
            "description": "The `key` returned by `/api/diff/upload-url`."
          },
          "fileName": {
            "type": "string",
            "description": "The file name with its extension; it picks the converter."
          }
        }
      },
      "DiffShareLink": {
        "type": "object",
        "required": [
          "url",
          "token",
          "expiresAt",
          "access",
          "before",
          "after"
        ],
        "description": "A link to the diff of two files.",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The page that shows the diff. Give it to the user, or open it in a browser."
          },
          "token": {
            "type": "string",
            "description": "The last path segment of `url`."
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the link stops opening and the files are deleted."
          },
          "access": {
            "type": "string",
            "enum": [
              "link"
            ],
            "description": "Always `link`: the URL opens for anyone who holds it."
          },
          "before": {
            "$ref": "#/components/schemas/DiffShareSide"
          },
          "after": {
            "$ref": "#/components/schemas/DiffShareSide"
          }
        }
      },
      "DiffShareSide": {
        "type": "object",
        "required": [
          "fileName",
          "size"
        ],
        "properties": {
          "fileName": {
            "type": "string"
          },
          "size": {
            "type": "integer",
            "description": "Bytes."
          }
        }
      },
      "ExtractionResult": {
        "type": "object",
        "required": [
          "text",
          "format",
          "supported"
        ],
        "properties": {
          "text": {
            "type": "string",
            "description": "The file's text. Spreadsheets come back as CSV, one sheet after another."
          },
          "format": {
            "type": "string",
            "description": "The format Kord detected, as a short id."
          },
          "supported": {
            "type": "boolean",
            "description": "False when the format is versioned but has no text extraction; `text` is then a placeholder."
          },
          "structured": {
            "type": "object",
            "description": "Present for spreadsheets (`sheets`), Rockwell PLC projects (`plc`), FactoryTalk View displays (`hmi`) and G-code programs (`gcode`). Very large spreadsheet models are trimmed of cell formatting to fit the response.",
            "properties": {
              "sheets": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "plc": {
                "type": "object"
              },
              "hmi": {
                "type": "object"
              },
              "gcode": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "securitySchemes": {
      "oauth2": {
        "type": "oauth2",
        "description": "OAuth 2.1 through the authorization server named in /.well-known/oauth-protected-resource (https://ebfrepskmaicwvjinfhf.supabase.co/auth/v1). Dynamic client registration (RFC 7591) at https://ebfrepskmaicwvjinfhf.supabase.co/auth/v1/oauth/clients/register; PKCE S256; the scopes are OpenID identity scopes only - what a token may do is the signed-in user's own permissions.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://ebfrepskmaicwvjinfhf.supabase.co/auth/v1/oauth/authorize",
            "tokenUrl": "https://ebfrepskmaicwvjinfhf.supabase.co/auth/v1/oauth/token",
            "refreshUrl": "https://ebfrepskmaicwvjinfhf.supabase.co/auth/v1/oauth/token",
            "scopes": {
              "openid": "Identify the user.",
              "email": "The user's email address.",
              "profile": "The user's name and picture."
            }
          }
        }
      }
    }
  }
}