Apideck TypeScript SDK Skill
Overview
The Apideck Unified API provides a single integration layer to connect with 200+ third-party services across accounting, CRM, HRIS, file storage, ATS, e-commerce, and more. The official TypeScript SDK (@apideck/unify) provides typed clients for all unified APIs.
Key capabilities:
- Accounting - Invoices, bills, payments, ledger accounts, journal entries, tax rates, balance sheets, P&L
- CRM - Contacts, companies, leads, opportunities, activities, pipelines, notes
- HRIS - Employees, departments, payrolls, time-off requests, schedules
- File Storage - Files, folders, drives, shared links, upload sessions
- ATS - Jobs, applicants, applications
- Vault - Connection management, OAuth flows, custom field mapping
- Vault JS - Embeddable modal UI for users to authorize connectors and manage settings
- Webhook - Event subscriptions and real-time notifications
Installation
npm add @apideck/unify
Requires Node.js 18+. The SDK is fully typed with TypeScript definitions.
IMPORTANT RULES
- ALWAYS use the
@apideck/unify SDK. DO NOT make raw fetch/axios calls to the Apideck API.
- ALWAYS pass
apiKey, appId, and consumerId when initializing the client. These are required for all API calls.
- ALWAYS set the
APIDECK_API_KEY environment variable rather than hardcoding API keys.
- USE
serviceId to specify which downstream connector to use (e.g., "salesforce", "quickbooks", "xero"). If a consumer has multiple connections for an API, serviceId is required.
- USE cursor-based pagination with
for await...of for iterating large result sets. DO NOT implement manual pagination.
- USE the
filter parameter to narrow results server-side. DO NOT fetch all records and filter client-side.
- USE the
fields parameter to request only the columns you need. This reduces response size and improves performance.
- ALWAYS handle errors with try/catch. The SDK throws typed errors for different HTTP status codes.
- DO NOT store Apideck API keys, App IDs, or Consumer IDs in source code. Use environment variables or a secrets manager.
Quick Start
import { Apideck } from "@apideck/unify";
const apideck = new Apideck({
apiKey: process.env["APIDECK_API_KEY"] ?? "",
appId: "your-app-id",
consumerId: "your-consumer-id",
});
// List CRM contacts
const { data } = await apideck.crm.contacts.list({
limit: 20,
filter: { email: "john@example.com" },
});
for (const contact of data) {
console.log(contact.name, contact.emails);
}
SDK Patterns
Client Setup
import { Apideck } from "@apideck/unify";
const apideck = new Apideck({
apiKey: process.env["APIDECK_API_KEY"] ?? "",
appId: "your-app-id",
consumerId: "your-consumer-id",
});
The consumerId identifies the end-user whose connections are being used. In multi-tenant apps, set this per-request or per-user session.
CRUD Operations
All resources follow the same pattern: apideck.{api}.{resource}.{operation}().
// LIST - retrieve multiple records
const { data } = await apideck.crm.contacts.list({
serviceId: "salesforce",
limit: 20,
filter: { email: "john@example.com" },
sort: { by: "updated_at", direction: "desc" },
fields: "id,name,email,phone_numbers",
});
// CREATE - create a new record
const { data: created } = await apideck.crm.contacts.create({
serviceId: "salesforce",
contact: {
first_name: "John",
last_name: "Doe",
emails: [{ email: "john@example.com", type: "primary" }],
phone_numbers: [{ number: "+1234567890", type: "mobile" }],
},
});
console.log(created.id); // "contact_abc123"
// GET - retrieve a single record
const { data: contact } = await apideck.crm.contacts.get({
id: "contact_abc123",
serviceId: "salesforce",
});
// UPDATE - modify an existing record
const { data: updated } = await apideck.crm.contacts.update({
id: "contact_abc123",
serviceId: "salesforce",
contact: { first_name: "Jane" },
});
// DELETE - remove a record
await apideck.crm.contacts.delete({
id: "contact_abc123",
serviceId: "salesforce",
});
Pagination
Use async iteration to automatically handle cursor-based pagination:
const result = await apideck.accounting.invoices.list({
serviceId: "quickbooks",
limit: 50,
});
// Automatically fetches next pages
for await (const page of result) {
for (const invoice of page.data) {
console.log(invoice.number, invoice.total);
}
}
Or handle pagination manually:
let cursor: string | undefined;
do {
const { data, meta } = await apideck.accounting.invoices.list({
serviceId: "quickbooks",
limit: 50,
cursor,
});
for (const invoice of data) {
console.log(invoice.number);
}
cursor = meta?.cursors?.next ?? undefined;
} while (cursor);
Error Handling
import { Apideck } from "@apideck/unify";
import * as errors from "@apideck/unify/models/errors";
try {
const { data } = await apideck.crm.contacts.get({ id: "invalid" });
} catch (e) {
if (e instanceof errors.BadRequestResponse) {
console.error("Bad request:", e.message);
} else if (e instanceof errors.UnauthorizedResponse) {
console.error("Invalid API key or missing credentials");
} else if (e instanceof errors.NotFoundResponse) {
console.error("Record not found");
} else if (e instanceof errors.PaymentRequiredResponse) {
console.error("API limit reached or payment required");
} else if (e instanceof errors.UnprocessableResponse) {
console.error("Validation error:", e.message);
} else {
throw e;
}
}
Common Parameters
Most list endpoints accept these parameters:
| Parameter |
Type |
Description |
serviceId |
string |
Downstream connector ID (e.g., "quickbooks", "salesforce") |
limit |
number |
Max results per page (1-200, default 20) |
cursor |
string |
Pagination cursor from previous response |
filter |
object |
Resource-specific filter criteria |
sort |
object |
{ by: string, direction: "asc" | "desc" } |
fields |
string |
Comma-separated field names to return |
passThrough |
object |
Pass-through query parameters for the downstream API |
Pass-Through Parameters
When the unified model doesn't cover a connector-specific field, use passThrough:
const { data } = await apideck.accounting.invoices.list({
serviceId: "quickbooks",
passThrough: {
search: "overdue",
},
});
For creating/updating, use pass_through in the request body to send connector-specific fields:
const { data } = await apideck.crm.contacts.create({
serviceId: "salesforce",
contact: {
first_name: "John",
last_name: "Doe",
pass_through: [
{
service_id: "salesforce",
operation_id: "contactsAdd",
extend_object: { custom_sf_field__c: "value" },
},
],
},
});
API Namespaces
The SDK organizes APIs by namespace. See the reference files for detailed endpoints:
| Namespace |
Reference |
Resources |
apideck.accounting.* |
references/accounting-api.md |
invoices, bills, payments, customers, suppliers, ledgerAccounts, journalEntries, taxRates, creditNotes, purchaseOrders, balanceSheet, profitAndLoss, and more |
apideck.crm.* |
references/crm-api.md |
contacts, companies, leads, opportunities, activities, notes, pipelines, users |
apideck.hris.* |
references/hris-api.md |
employees, companies, departments, payrolls, timeOffRequests |
apideck.fileStorage.* |
references/file-storage-api.md |
files, folders, drives, driveGroups, sharedLinks, uploadSessions |
apideck.ats.* |
references/ats-api.md |
applicants, applications, jobs |
apideck.vault.* |
references/vault-api.md |
connections, connectionSettings, consumers, customMappings, logs, sessions |
apideck.webhook.* |
references/webhook-api.md |
webhooks, eventLogs |
Vault JS (Embeddable UI)
Use @apideck/vault-js to embed a pre-built modal that lets your users authorize connectors and manage integration settings. Session creation must happen server-side.
// 1. Server-side: create a session
const { data } = await apideck.vault.sessions.create({
session: {
consumer_metadata: { account_name: "Acme Corp", user_name: "John Doe", email: "john@acme.com" },
redirect_uri: "https://myapp.com/integrations",
settings: { unified_apis: ["accounting", "crm"] },
theme: { vault_name: "My App", primary_color: "#4F46E5" },
},
});
// 2. Client-side: open the modal
import { ApideckVault } from "@apideck/vault-js";
ApideckVault.open({
token: sessionToken,
onConnectionChange: (connection) => console.log("Changed:", connection),
onClose: () => console.log("Closed"),
});
See references/vault-js.md for full configuration options, theming, React integration, and event callbacks.
1---2name: apideck-node3description: Apideck Unified API integration patterns for TypeScript and Node.js. Use when building integrations with accounting software (QuickBooks, Xero, NetSuite), CRMs (Salesforce, HubSpot, Pipedrive), HRIS platforms (Workday, BambooHR), file storage (Google Drive, Dropbox, Box), ATS systems (Greenhouse, Lever), e-commerce, or any of Apideck's 200+ connectors. Covers the @apideck/unify SDK, authentication, CRUD operations, pagination, filtering, webhooks, and Vault connection management.4license: Apache-2.05---67# Apideck TypeScript SDK Skill89## Overview1011The [Apideck Unified API](https://apideck.com) provides a single integration layer to connect with 200+ third-party services across accounting, CRM, HRIS, file storage, ATS, e-commerce, and more. The official TypeScript SDK (`@apideck/unify`) provides typed clients for all unified APIs.1213Key capabilities:14- **Accounting** - Invoices, bills, payments, ledger accounts, journal entries, tax rates, balance sheets, P&L15- **CRM** - Contacts, companies, leads, opportunities, activities, pipelines, notes16- **HRIS** - Employees, departments, payrolls, time-off requests, schedules17- **File Storage** - Files, folders, drives, shared links, upload sessions18- **ATS** - Jobs, applicants, applications19- **Vault** - Connection management, OAuth flows, custom field mapping20- **Vault JS** - Embeddable modal UI for users to authorize connectors and manage settings21- **Webhook** - Event subscriptions and real-time notifications2223## Installation2425```sh26npm add @apideck/unify27```2829Requires Node.js 18+. The SDK is fully typed with TypeScript definitions.3031## IMPORTANT RULES3233- ALWAYS use the `@apideck/unify` SDK. DO NOT make raw `fetch`/`axios` calls to the Apideck API.34- ALWAYS pass `apiKey`, `appId`, and `consumerId` when initializing the client. These are required for all API calls.35- ALWAYS set the `APIDECK_API_KEY` environment variable rather than hardcoding API keys.36- USE `serviceId` to specify which downstream connector to use (e.g., `"salesforce"`, `"quickbooks"`, `"xero"`). If a consumer has multiple connections for an API, `serviceId` is required.37- USE cursor-based pagination with `for await...of` for iterating large result sets. DO NOT implement manual pagination.38- USE the `filter` parameter to narrow results server-side. DO NOT fetch all records and filter client-side.39- USE the `fields` parameter to request only the columns you need. This reduces response size and improves performance.40- ALWAYS handle errors with try/catch. The SDK throws typed errors for different HTTP status codes.41- DO NOT store Apideck API keys, App IDs, or Consumer IDs in source code. Use environment variables or a secrets manager.4243## Quick Start4445```typescript46import { Apideck } from "@apideck/unify";4748const apideck = new Apideck({49 apiKey: process.env["APIDECK_API_KEY"] ?? "",50 appId: "your-app-id",51 consumerId: "your-consumer-id",52});5354// List CRM contacts55const { data } = await apideck.crm.contacts.list({56 limit: 20,57 filter: { email: "john@example.com" },58});5960for (const contact of data) {61 console.log(contact.name, contact.emails);62}63```6465## SDK Patterns6667### Client Setup6869```typescript70import { Apideck } from "@apideck/unify";7172const apideck = new Apideck({73 apiKey: process.env["APIDECK_API_KEY"] ?? "",74 appId: "your-app-id",75 consumerId: "your-consumer-id",76});77```7879The `consumerId` identifies the end-user whose connections are being used. In multi-tenant apps, set this per-request or per-user session.8081### CRUD Operations8283All resources follow the same pattern: `apideck.{api}.{resource}.{operation}()`.8485```typescript86// LIST - retrieve multiple records87const { data } = await apideck.crm.contacts.list({88 serviceId: "salesforce",89 limit: 20,90 filter: { email: "john@example.com" },91 sort: { by: "updated_at", direction: "desc" },92 fields: "id,name,email,phone_numbers",93});9495// CREATE - create a new record96const { data: created } = await apideck.crm.contacts.create({97 serviceId: "salesforce",98 contact: {99 first_name: "John",100 last_name: "Doe",101 emails: [{ email: "john@example.com", type: "primary" }],102 phone_numbers: [{ number: "+1234567890", type: "mobile" }],103 },104});105console.log(created.id); // "contact_abc123"106107// GET - retrieve a single record108const { data: contact } = await apideck.crm.contacts.get({109 id: "contact_abc123",110 serviceId: "salesforce",111});112113// UPDATE - modify an existing record114const { data: updated } = await apideck.crm.contacts.update({115 id: "contact_abc123",116 serviceId: "salesforce",117 contact: { first_name: "Jane" },118});119120// DELETE - remove a record121await apideck.crm.contacts.delete({122 id: "contact_abc123",123 serviceId: "salesforce",124});125```126127### Pagination128129Use async iteration to automatically handle cursor-based pagination:130131```typescript132const result = await apideck.accounting.invoices.list({133 serviceId: "quickbooks",134 limit: 50,135});136137// Automatically fetches next pages138for await (const page of result) {139 for (const invoice of page.data) {140 console.log(invoice.number, invoice.total);141 }142}143```144145Or handle pagination manually:146147```typescript148let cursor: string | undefined;149do {150 const { data, meta } = await apideck.accounting.invoices.list({151 serviceId: "quickbooks",152 limit: 50,153 cursor,154 });155 for (const invoice of data) {156 console.log(invoice.number);157 }158 cursor = meta?.cursors?.next ?? undefined;159} while (cursor);160```161162### Error Handling163164```typescript165import { Apideck } from "@apideck/unify";166import * as errors from "@apideck/unify/models/errors";167168try {169 const { data } = await apideck.crm.contacts.get({ id: "invalid" });170} catch (e) {171 if (e instanceof errors.BadRequestResponse) {172 console.error("Bad request:", e.message);173 } else if (e instanceof errors.UnauthorizedResponse) {174 console.error("Invalid API key or missing credentials");175 } else if (e instanceof errors.NotFoundResponse) {176 console.error("Record not found");177 } else if (e instanceof errors.PaymentRequiredResponse) {178 console.error("API limit reached or payment required");179 } else if (e instanceof errors.UnprocessableResponse) {180 console.error("Validation error:", e.message);181 } else {182 throw e;183 }184}185```186187### Common Parameters188189Most list endpoints accept these parameters:190191| Parameter | Type | Description |192|-----------|------|-------------|193| `serviceId` | `string` | Downstream connector ID (e.g., `"quickbooks"`, `"salesforce"`) |194| `limit` | `number` | Max results per page (1-200, default 20) |195| `cursor` | `string` | Pagination cursor from previous response |196| `filter` | `object` | Resource-specific filter criteria |197| `sort` | `object` | `{ by: string, direction: "asc" \| "desc" }` |198| `fields` | `string` | Comma-separated field names to return |199| `passThrough` | `object` | Pass-through query parameters for the downstream API |200201### Pass-Through Parameters202203When the unified model doesn't cover a connector-specific field, use `passThrough`:204205```typescript206const { data } = await apideck.accounting.invoices.list({207 serviceId: "quickbooks",208 passThrough: {209 search: "overdue",210 },211});212```213214For creating/updating, use `pass_through` in the request body to send connector-specific fields:215216```typescript217const { data } = await apideck.crm.contacts.create({218 serviceId: "salesforce",219 contact: {220 first_name: "John",221 last_name: "Doe",222 pass_through: [223 {224 service_id: "salesforce",225 operation_id: "contactsAdd",226 extend_object: { custom_sf_field__c: "value" },227 },228 ],229 },230});231```232233## API Namespaces234235The SDK organizes APIs by namespace. See the reference files for detailed endpoints:236237| Namespace | Reference | Resources |238|-----------|-----------|-----------|239| `apideck.accounting.*` | [references/accounting-api.md](references/accounting-api.md) | invoices, bills, payments, customers, suppliers, ledgerAccounts, journalEntries, taxRates, creditNotes, purchaseOrders, balanceSheet, profitAndLoss, and more |240| `apideck.crm.*` | [references/crm-api.md](references/crm-api.md) | contacts, companies, leads, opportunities, activities, notes, pipelines, users |241| `apideck.hris.*` | [references/hris-api.md](references/hris-api.md) | employees, companies, departments, payrolls, timeOffRequests |242| `apideck.fileStorage.*` | [references/file-storage-api.md](references/file-storage-api.md) | files, folders, drives, driveGroups, sharedLinks, uploadSessions |243| `apideck.ats.*` | [references/ats-api.md](references/ats-api.md) | applicants, applications, jobs |244| `apideck.vault.*` | [references/vault-api.md](references/vault-api.md) | connections, connectionSettings, consumers, customMappings, logs, sessions |245| `apideck.webhook.*` | [references/webhook-api.md](references/webhook-api.md) | webhooks, eventLogs |246247## Vault JS (Embeddable UI)248249Use [`@apideck/vault-js`](references/vault-js.md) to embed a pre-built modal that lets your users authorize connectors and manage integration settings. Session creation must happen server-side.250251```typescript252// 1. Server-side: create a session253const { data } = await apideck.vault.sessions.create({254 session: {255 consumer_metadata: { account_name: "Acme Corp", user_name: "John Doe", email: "john@acme.com" },256 redirect_uri: "https://myapp.com/integrations",257 settings: { unified_apis: ["accounting", "crm"] },258 theme: { vault_name: "My App", primary_color: "#4F46E5" },259 },260});261262// 2. Client-side: open the modal263import { ApideckVault } from "@apideck/vault-js";264265ApideckVault.open({266 token: sessionToken,267 onConnectionChange: (connection) => console.log("Changed:", connection),268 onClose: () => console.log("Closed"),269});270```271272See [references/vault-js.md](references/vault-js.md) for full configuration options, theming, React integration, and event callbacks.