A typed TypeScript SDK for the MOCO ERP API. It is generated from the cloned mocoapp-api-docs documentation and currently covers every documented endpoint in mocoapp-api-docs/sections.
Last MOCO API feature check with findings: 2026-05-08T18:19:15.395Z
npm install @citation-media/moco-sdkFor local development in this repository:
git submodule update --init --recursive
npm install
npm run generate
npm testimport { MocoClient } from "@citation-media/moco-sdk";
const moco = new MocoClient({
subdomain: "your-company",
apiKey: process.env.MOCO_API_KEY,
});
const projects = await moco.projects.list({
include_archived: false,
updated_after: "2026-01-01T00:00:00Z",
sort_by: "name asc",
});
console.log(projects.data);
console.log(projects.pagination);MOCO uses a dedicated URL per customer. Configure the client with whichever form matches your account:
new MocoClient({ subdomain: "example", apiKey: "..." });
new MocoClient({ domain: "example.mocoapp.com", apiKey: "..." });
new MocoClient({ baseUrl: "https://example.mocoapp.com/api/v1", apiKey: "..." });Authentication defaults to MOCO's token header:
Authorization: Token token=YOUR_API_KEYBearer tokens and fully custom authorization headers are also supported:
new MocoClient({ subdomain: "example", apiKey: "...", authScheme: "bearer" });
new MocoClient({ baseUrl: "https://example.mocoapp.com/api/v1", authorizationHeader: "Bearer ..." });const project = await moco.projects.get({ id: 123 });
await moco.activities.create({
date: "2026-05-08",
project_id: 123,
task_id: 456,
seconds: 3600,
description: "Implementation",
});
await moco.projects.archive({ id: 123 });Generated resources are grouped by MOCO domain, for example projects, activities, companies, invoices, purchases, users, offers, deals, tags, webHooks, and account resources such as accountTaskTemplates.
The full generated endpoint map is exported as MOCO_ENDPOINTS and documented in docs/API_COVERAGE.md.
List methods include MOCO's pagination parameters, sorting, global filters, and entity-specific filters from the docs:
const response = await moco.companies.list({
page: 1,
per_page: 100,
sort_by: "name desc",
ids: [123, 456],
updated_after: new Date("2026-01-01T00:00:00Z"),
type: "customer",
tags: "Automotive, Pharma",
custom_properties: {
Sector: ["Pharma", "Chemistry"],
Newsletter: true,
},
});Every list endpoint also has an All helper that follows MOCO's Link: rel="next" pagination header:
for await (const activity of moco.activities.listAll({ from: "2026-05-01", to: "2026-05-31" })) {
console.log(activity.id);
}Set X-IMPERSONATE-USER-ID globally or per request:
const asUser = moco.withImpersonation(933590696);
await asUser.activities.create({ date: "2026-05-08", project_id: 1, task_id: 2, seconds: 900 });
await moco.activities.list({}, { impersonateUserId: 933590696 });MOCO returns 429 Too Many Requests when the account limit is exceeded. The SDK throws MocoRateLimitError with retry metadata from the response headers.
import { MocoRateLimitError } from "@citation-media/moco-sdk";
try {
await moco.projects.list();
} catch (error) {
if (error instanceof MocoRateLimitError) {
console.log(error.retryAfterSeconds);
console.log(error.rateLimit);
}
}Automatic retries for 429 responses can be enabled:
const moco = new MocoClient({
subdomain: "example",
apiKey: "...",
rateLimit: { maxRetries: 2 },
});MOCO signs webhook payloads with HMAC-SHA256. Verify the raw request payload before parsing or trusting it:
import { createWebhookEnvelope, verifyWebhookRequest } from "@citation-media/moco-sdk";
const rawBody = await request.text();
const valid = await verifyWebhookRequest(rawBody, request.headers, process.env.MOCO_WEBHOOK_SIGNATURE_KEY!);
if (!valid) {
throw new Error("Invalid MOCO webhook signature");
}
const envelope = createWebhookEnvelope(JSON.parse(rawBody), request.headers);
console.log(envelope.target, envelope.event, envelope.userId);If MOCO adds a route before this SDK is regenerated, use the typed low-level request API:
const response = await moco.request({
method: "POST",
path: "/experimental_route",
body: { enabled: true },
});The upstream MOCO API docs are tracked as a Git submodule at mocoapp-api-docs. This keeps the docs related to the SDK without vendoring the full docs repository into this repository's history.
Clone with submodules:
git clone --recurse-submodules <repo-url>If you already cloned the repository:
git submodule update --init --recursiveUpdate to the latest upstream docs and regenerate the SDK:
git submodule update --remote mocoapp-api-docs
npm run generate
npm testCommit the submodule pointer together with generated SDK changes:
git add mocoapp-api-docs src/generated docs/API_COVERAGE.md
git commit -m "Update generated SDK from MOCO API docs"npm run generate # Rebuild resources and API coverage from mocoapp-api-docs
npm run build # Type-check and emit dist
npm test # Build and run SDK behavior testsThis repository uses Flue to watch for new MOCO REST API functionality.
MOCO API Watchruns every Monday at 06:00 UTC and can also be triggered manually from GitHub Actions.- Both Flue agents use the direct Workers AI model
cloudflare-workers-ai/@cf/moonshotai/kimi-k2.6. - The watch agent updates the docs submodule in its CI workspace, regenerates
docs/API_COVERAGE.md, checkshttps://www.mocoapp.com/blog.atom, and opens one issue per new API-relevant finding. - Blog posts are treated as supplemental references only. The watch agent creates issues only for concrete upstream docs/API coverage additions that still need SDK work.
- The watch agent updates the
Last MOCO API feature check with findingstimestamp above by direct commit only when it creates at least one issue. MOCO API PR Agentruns on trustedmoco-api-updateissues and opens implementation PRs.- Issues created by
github-actions[bot]are trusted only when they include the expected Flue labels and hidden finding marker. Human-created issues must be authored by a repo collaborator withwrite,maintain, oradminpermission. - Add
CLOUDFLARE_ACCOUNT_IDandCLOUDFLARE_API_TOKENas GitHub Actions repository secrets before enabling the workflows. The workflow mapsCLOUDFLARE_API_TOKENto theCLOUDFLARE_API_KEYenv var expected by Flue's Workers AI provider. - Allow GitHub Actions to create pull requests in the repository settings, or add
FLUE_GITHUB_TOKENas a repository secret containing a PAT with access to create branches, issues, workflow dispatches, and pull requests. - Package publishing uses npm trusted publishing with GitHub Actions OIDC. Configure
@citation-media/moco-sdkon npm with this repository and workflow.github/workflows/deploy.yml; noNPM_TOKENsecret is required for that path.
Manual runs:
cp .env.example .env
# Fill GITHUB_TOKEN/GH_TOKEN and Cloudflare AI Gateway values in .env, then load them:
set -a && source .env && set +a
npm run flue:moco-api-watch -- --payload '{"dryRun":true}'
npm run flue:moco-api-pr -- --payload '{"issueNumber":123,"dryRun":true}'