fix(server): document the company export request body in the API catalog (#19945)

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - A CEO agent can export the company setup through `POST
/api/companies/{companyId}/exports`
> - Agents find API operations and their request bodies in the OpenAPI
catalog
> - The catalog entries for `/exports` and `/exports/preview` have no
request body
> - The export defaults include only the company and agents, so an agent
that sends no body gets a package without projects or skills
> - This pull request registers the existing export body schema on both
entries and describes the `include` defaults
> - The benefit is that agents can request a complete reusable setup and
do not tell users that resources do not exist

## Linked Issues or Issue Description

**What happened**

A user asked a CEO agent in chat: "Export this company's setup so I can
reuse it." The company had an agent, a project, and a company skill. The
agent looked up `POST /api/companies/{companyId}/exports` in the API
catalog. Its reasoning said that the operation "has no request body
defined". It sent no body. The package had only the company and the
agent. The agent then told the user that the company had "no projects,
issues, or company skills, so nothing else was left out." That statement
was false.

**Expected behavior**

The catalog documents the export body, including `include.projects` and
`include.skills`. An agent can request a full setup export. The legacy
`/export` route already documents this body.

**Steps to reproduce**

1. Create a company with a project and a company skill.
2. Read `GET /api/openapi.json`,
`paths["/api/companies/{companyId}/exports"].post`.
3. See that it has no `requestBody`. The legacy `/export` entry has one.
4. Call `POST /exports` with no body. The package has no projects or
skills.

## What Changed

- Register `companyPortabilityExportSchema` as the request body of `POST
/api/companies/{companyId}/exports` and `/exports/preview` in
`server/src/routes/openapi.ts`.
- Add a description that states the `include` defaults and tells callers
to set `include.projects` and `include.skills` for a reusable setup.
- Add a regression test in
`server/src/__tests__/openapi-routes.test.ts`.

## Verification

- `cd server && pnpm exec vitest run
src/__tests__/openapi-routes.test.ts` passes (36 tests). The new test
fails on master because the entries have no `requestBody`.
- An end-to-end check runs the `companies.export-company` chat eval with
this commit cherry-picked onto the eval branch.

## Risks

- Low risk. This changes only API documentation. The routes already
parse this schema. Runtime behavior does not change.
- `imports/preview` and `imports/apply` also register no body. This PR
does not change them.

## Model Used

- Anthropic Claude Opus 5.5 (`claude-opus-5-5`), with tool use and code
execution in Claude Code.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
DottaandClaude Opus 5.5 authored and GitHub committed 2026-11-07 20:55:44 -06:00
1 parent 2a2d3f6e23
commit 468f22ae4b
2 files changed
+19 -2

No files matched your search

@@ -331,6 +331,15 @@ function loadSpecRoutes() {
}
describe("openapi routes", () => {
it("documents the export include body so agents can request projects and skills", () => {
const spec = buildOpenApiSpec();
for (const path of ["/api/companies/{companyId}/exports", "/api/companies/{companyId}/exports/preview"]) {
const operation = spec.paths[path].post;
const body = operation.requestBody.content["application/json"].schema;
expect(body.properties.include.properties).toMatchObject({ projects: expect.anything(), skills: expect.anything() });
expect(operation.description).toMatch(/include\.projects/);
}
});
it("documents generic webhook provenance headers and accepted responses without a provider-specific endpoint", () => {
const spec = buildOpenApiSpec();
const operation = spec.paths["/api/routine-triggers/public/{publicId}/fire"].post;
+10 -2
View File
@@ -2187,7 +2187,11 @@ registry.registerPath({
path: "/api/companies/{companyId}/exports",
tags: ["companies"],
summary: "Export company data",
request: { params: z.object({ companyId: z.string() }) },
description: "Omitting include exports only the company and agents (projects, skills and issues default to false); set include.projects and include.skills for a reusable company setup.",
request: {
params: z.object({ companyId: z.string() }),
body: jsonBody(companyPortabilityExportSchema),
},
responses: { 200: r.ok(), 401: r.unauthorized },
});
@@ -2196,7 +2200,11 @@ registry.registerPath({
path: "/api/companies/{companyId}/exports/preview",
tags: ["companies"],
summary: "Preview company export",
request: { params: z.object({ companyId: z.string() }) },
description: "Omitting include exports only the company and agents (projects, skills and issues default to false); set include.projects and include.skills for a reusable company setup.",
request: {
params: z.object({ companyId: z.string() }),
body: jsonBody(companyPortabilityExportSchema),
},
responses: { 200: r.ok(), 401: r.unauthorized },
});