# API and resumable file transfer

Set `CODEMERCHANT_URL` to the owner's CodeMerchant origin. Send
`Authorization: Bearer $CODEMERCHANT_API_TOKEN` to its API only. Obtain the token
through the sign-in steps in SKILL.md. Download
`/agent/workflow.json` for exact request schemas, permissions, limits, MCP URL,
human handoffs and OpenAPI link. All control requests/responses use JSON. MCP
provides the same operations, except raw file bytes require an HTTP client.

## File upload

1. POST `/api/v1/context-uploads` with `{ "action": "initiate", "repository":
   "acme/engine", "filename": "linear-project.md", "size": 12345 }` using the
   file's actual byte size. Save the returned `id` and private `uploadUrl` locally.
2. PUT binary chunks to `uploadUrl` with `Content-Type: application/octet-stream`
   and `Content-Range: bytes START-END/TOTAL` (END inclusive). Use the manifest's
   `chunkBytes`. Accept intermediate HTTP 308 as resumable progress; do not follow
   it as a redirect. Never send the CodeMerchant bearer token to the upload host.
3. POST the same control endpoint with `{ "action": "complete", "repository":
   "acme/engine", "id": "RETURNED_ID" }`. Confirm storage; bytes transferred
   alone are not a saved attachment. Retry this operation after a confirmation
   failure, using the same ID, rather than uploading another copy.
4. Poll `{ "action": "status", "repository": "acme/engine", "id": "..." }`
   with the manifest poll interval and bounded deadline. `uploading`, `queued`
   and `processing` are pending. `failed` is not usable. Only `complete` may be
   passed to scans. If extraction times out, preserve the ID and resume polling.
5. Use `{ "action": "list", "repository": "acme/engine" }` and follow returned
   `cursor` values to verify saved attachments before retries.
6. POST `/api/v1/context-scans` with `{ "repository": "acme/engine", "contextIds":
   ["..."], "refresh": true }` after extraction or source fixes. The IDs must
   belong to that user/repository. Include all intended IDs on every context
   rescan; ordinary scan/submission refresh does not reattach earlier context.

The bundled Python 3 helper performs steps 1–4, writes a mode-600 receipt with
ID/capability before transfer, sends bounded chunks, and prints only the final
public status. Example (run from the installed skill directory):

```sh
python3 scripts/upload-context.py acme/engine /private/reviewed-linear.md \
  --receipt /private/intake/linear-upload.json
```

Reuse the same receipt command after interruption. It verifies origin, repository,
file size/hash, checks existing status and resumes transfer using the service's
resumable offset. Keep the receipt private: its upload URL is a capability.
Do not change the source file while a transfer is in progress.

## Scans and submission

POST `/api/v1/scans` accepts `{ "mode": "selected", "repositories":
["acme/engine"] }` or explicit owner-authorized `{ "mode": "all" }`. Selected
batches are bounded by `limits.repositoriesPerBatch`. Examine `scanned`, `failed`
and `remaining`; back off if no progress, never loop indefinitely.

GET `/api/v1/reports/acme/engine` returns `repository` and a nullable `report`.
POST `/api/v1/submissions` accepts `{ "repositories": ["acme/engine"] }`.
Read GET `/api/v1/submissions` afterward and match every full name to its saved ID
and stage. Retrying submission is deduplicated, but do not retry an ambiguous
response without reading saved state. Split requests below `submissionBodyBytes`.
Review-stage changes are owner/admin-only. License acceptance through
`POST /api/v1/data-license` requires the owner's explicit yes (see SKILL.md).

401 means missing/expired/insufficient-scope credentials. 403 means a license or
permission requirement. 400 means invalid selection, ownership, or context input;
409 means busy/unavailable state. Respect error codes and event references. The
manifest lists which backend services are configured; configuration is not proof
that an integration is healthy. Record unavailable services as blockers.

For an existing completed or failed upload, POST `/api/v1/context-uploads` with
`{ "action": "scope", "repository": "owner/repo", "id": "upload-id", "organizationWide": true }`
to share with its organization and queue fresh repository matching. Set `organizationWide`
to `false` to restrict it to the selected repository. Originals keep their existing IDs
and storage location. Scope edits are blocked while extraction is active. Refresh
status after a failed request before retrying; rerun analyses to replace old reports.
