diff --git a/.github/workflows/validate-skills.yml b/.github/workflows/validate-skills.yml new file mode 100644 index 0000000..06f06c9 --- /dev/null +++ b/.github/workflows/validate-skills.yml @@ -0,0 +1,80 @@ +name: Validate Skills + +on: + push: + branches: [main] + paths: + - 'skills/**' + - 'llms.txt' + pull_request: + branches: [main] + paths: + - 'skills/**' + - 'llms.txt' + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Validate SKILL.md frontmatter + run: | + errors=0 + for skill_dir in skills/apideck-*/; do + skill_name=$(basename "$skill_dir") + skill_file="$skill_dir/SKILL.md" + + if [ ! -f "$skill_file" ]; then + echo "ERROR: $skill_dir missing SKILL.md" + errors=$((errors + 1)) + continue + fi + + # Check name field matches directory + name_field=$(sed -n '/^---$/,/^---$/p' "$skill_file" | grep '^name:' | head -1 | sed 's/name: *//') + if [ "$name_field" != "$skill_name" ]; then + echo "ERROR: $skill_file name '$name_field' does not match directory '$skill_name'" + errors=$((errors + 1)) + fi + + # Check required frontmatter fields + for field in name description license alwaysApply; do + if ! sed -n '/^---$/,/^---$/p' "$skill_file" | grep -q "^${field}:"; then + echo "ERROR: $skill_file missing required field: $field" + errors=$((errors + 1)) + fi + done + + # Check metadata.json exists + if [ ! -f "$skill_dir/metadata.json" ]; then + echo "WARN: $skill_dir missing metadata.json" + fi + + # Check SKILL.md line count + lines=$(wc -l < "$skill_file") + if [ "$lines" -gt 500 ]; then + echo "WARN: $skill_file is $lines lines (recommended: under 500)" + fi + + echo "OK: $skill_name ($lines lines)" + done + + if [ $errors -gt 0 ]; then + echo "" + echo "FAILED: $errors error(s) found" + exit 1 + fi + + echo "" + echo "All skills validated successfully" + + - name: Validate provider sync + run: | + node skills/sync.js --skills-only + if [ -n "$(git diff --name-only providers/)" ]; then + echo "WARN: Provider mirrors are out of sync. Run 'node skills/sync.js --skills-only' and commit." + git diff --stat providers/ + else + echo "OK: Provider mirrors are in sync" + fi diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..af98c3a --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +node_modules/ +.env +*.local +specs-summary.json diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..3e73de6 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,100 @@ +# Agent Guidelines for Apideck API Skills + +This repository contains AI agent skills for the Apideck Unified API. Follow these guidelines when contributing or modifying skills. + +## Repository Structure + +``` +skills/{skill-name}/ + SKILL.md # Required: Agent instructions with YAML frontmatter + metadata.json # Required: Version, references, organization metadata + references/ # Optional: Detailed reference docs (loaded on demand) + scripts/ # Optional: Executable scripts +``` + +Provider mirrors (auto-generated via `node skills/sync.js`): +``` +providers/{claude,cursor}/plugin/ + skills/ # Mirror of skills/ (do not edit directly) + commands/ # Slash commands + .{provider}-plugin/ # Plugin configuration +``` + +## Skill Authoring Rules + +### SKILL.md Requirements + +- **Frontmatter** must include: `name`, `description`, `license`, `alwaysApply`, `metadata` (with `author` and `version`) +- **`name`** must be kebab-case, match the directory name, and start with `apideck-` +- **`description`** must include trigger phrases — words/phrases that help agents decide when to activate the skill +- **`alwaysApply`** should be `false` for all skills (loaded only when contextually relevant) +- **SKILL.md body** should stay under 500 lines. Move detailed content to `references/` files +- **Code examples** must use real Apideck SDK patterns with correct method signatures +- **Doc links** should point to `https://developers.apideck.com/` for official docs + +### Progressive Disclosure (Context Efficiency) + +Skills are loaded in tiers to minimize context window usage: + +1. **Metadata** (~100 tokens): `name` and `description` from frontmatter — loaded at startup for ALL installed skills +2. **Instructions** (< 5000 tokens): Full SKILL.md body — loaded when skill is activated +3. **References** (as needed): Files in `references/` — loaded only when agent needs specific details + +Keep SKILL.md as a quick reference / index. Put detailed API endpoint docs, migration tables, and configuration references in separate files. + +### metadata.json Format + +```json +{ + "version": "1.0.0", + "organization": "Apideck", + "date": "February 2026", + "abstract": "Brief description of the skill's purpose", + "references": [ + "https://developers.apideck.com", + "https://apideck.com" + ] +} +``` + +### Important Conventions + +- All SDKs follow the same CRUD pattern: `client.{api}.{resource}.{operation}()` +- Authentication always requires: `apiKey`, `appId`, `consumerId` +- `serviceId` specifies the downstream connector (e.g., `salesforce`, `quickbooks`) +- Base URL is always `https://unify.apideck.com` +- OpenAPI specs are at `https://specs.apideck.com/{api-name}.yml` +- API Explorer URL format: `https://developers.apideck.com/api-explorer?id={api}` +- Never hardcode API keys in examples — use environment variables +- Connector count is 200+ + +## Sync Workflow + +After modifying skills, run the sync script to update provider directories: + +```bash +node skills/sync.js --skills-only +``` + +This copies all skills and commands to `providers/claude/plugin/` and `providers/cursor/plugin/`. + +## Adding a New Skill + +1. Create `skills/apideck-{name}/SKILL.md` with proper frontmatter +2. Create `skills/apideck-{name}/metadata.json` +3. Add references in `skills/apideck-{name}/references/` if needed +4. Run `node skills/sync.js --skills-only` to sync to providers +5. Update `README.md` with the new skill +6. Update `llms.txt` if the skill covers a new API or tool + +## Validation Checklist + +- [ ] `name` field matches directory name +- [ ] `name` is kebab-case with `apideck-` prefix +- [ ] `description` includes trigger phrases for agent matching +- [ ] `alwaysApply: false` is set +- [ ] `metadata.json` exists with version and references +- [ ] SKILL.md is under 500 lines +- [ ] Code examples use correct SDK patterns +- [ ] No hardcoded API keys in examples +- [ ] Provider mirrors are up to date (`node skills/sync.js --skills-only`) diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..3e73de6 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,100 @@ +# Agent Guidelines for Apideck API Skills + +This repository contains AI agent skills for the Apideck Unified API. Follow these guidelines when contributing or modifying skills. + +## Repository Structure + +``` +skills/{skill-name}/ + SKILL.md # Required: Agent instructions with YAML frontmatter + metadata.json # Required: Version, references, organization metadata + references/ # Optional: Detailed reference docs (loaded on demand) + scripts/ # Optional: Executable scripts +``` + +Provider mirrors (auto-generated via `node skills/sync.js`): +``` +providers/{claude,cursor}/plugin/ + skills/ # Mirror of skills/ (do not edit directly) + commands/ # Slash commands + .{provider}-plugin/ # Plugin configuration +``` + +## Skill Authoring Rules + +### SKILL.md Requirements + +- **Frontmatter** must include: `name`, `description`, `license`, `alwaysApply`, `metadata` (with `author` and `version`) +- **`name`** must be kebab-case, match the directory name, and start with `apideck-` +- **`description`** must include trigger phrases — words/phrases that help agents decide when to activate the skill +- **`alwaysApply`** should be `false` for all skills (loaded only when contextually relevant) +- **SKILL.md body** should stay under 500 lines. Move detailed content to `references/` files +- **Code examples** must use real Apideck SDK patterns with correct method signatures +- **Doc links** should point to `https://developers.apideck.com/` for official docs + +### Progressive Disclosure (Context Efficiency) + +Skills are loaded in tiers to minimize context window usage: + +1. **Metadata** (~100 tokens): `name` and `description` from frontmatter — loaded at startup for ALL installed skills +2. **Instructions** (< 5000 tokens): Full SKILL.md body — loaded when skill is activated +3. **References** (as needed): Files in `references/` — loaded only when agent needs specific details + +Keep SKILL.md as a quick reference / index. Put detailed API endpoint docs, migration tables, and configuration references in separate files. + +### metadata.json Format + +```json +{ + "version": "1.0.0", + "organization": "Apideck", + "date": "February 2026", + "abstract": "Brief description of the skill's purpose", + "references": [ + "https://developers.apideck.com", + "https://apideck.com" + ] +} +``` + +### Important Conventions + +- All SDKs follow the same CRUD pattern: `client.{api}.{resource}.{operation}()` +- Authentication always requires: `apiKey`, `appId`, `consumerId` +- `serviceId` specifies the downstream connector (e.g., `salesforce`, `quickbooks`) +- Base URL is always `https://unify.apideck.com` +- OpenAPI specs are at `https://specs.apideck.com/{api-name}.yml` +- API Explorer URL format: `https://developers.apideck.com/api-explorer?id={api}` +- Never hardcode API keys in examples — use environment variables +- Connector count is 200+ + +## Sync Workflow + +After modifying skills, run the sync script to update provider directories: + +```bash +node skills/sync.js --skills-only +``` + +This copies all skills and commands to `providers/claude/plugin/` and `providers/cursor/plugin/`. + +## Adding a New Skill + +1. Create `skills/apideck-{name}/SKILL.md` with proper frontmatter +2. Create `skills/apideck-{name}/metadata.json` +3. Add references in `skills/apideck-{name}/references/` if needed +4. Run `node skills/sync.js --skills-only` to sync to providers +5. Update `README.md` with the new skill +6. Update `llms.txt` if the skill covers a new API or tool + +## Validation Checklist + +- [ ] `name` field matches directory name +- [ ] `name` is kebab-case with `apideck-` prefix +- [ ] `description` includes trigger phrases for agent matching +- [ ] `alwaysApply: false` is set +- [ ] `metadata.json` exists with version and references +- [ ] SKILL.md is under 500 lines +- [ ] Code examples use correct SDK patterns +- [ ] No hardcoded API keys in examples +- [ ] Provider mirrors are up to date (`node skills/sync.js --skills-only`) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8328454 --- /dev/null +++ b/LICENSE @@ -0,0 +1,189 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work. + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to the Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by the Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding any notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2025 Apideck + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ed5ca0d --- /dev/null +++ b/README.md @@ -0,0 +1,136 @@ +# Apideck API Skills + +AI coding agent skills for the [Apideck](https://apideck.com) Unified API. + +These skills teach your AI agent SDK patterns, available methods, authentication setup, best practices, and API testing for integrating with 200+ connectors through Apideck's unified APIs. + +## Installation + +Install a skill for your language: + +```bash +# TypeScript / Node.js +npx skills add apideck/api-skills --skill apideck-node + +# Python +npx skills add apideck/api-skills --skill apideck-python + +# C# / .NET +npx skills add apideck/api-skills --skill apideck-dotnet + +# Java +npx skills add apideck/api-skills --skill apideck-java + +# Go +npx skills add apideck/api-skills --skill apideck-go + +# PHP +npx skills add apideck/api-skills --skill apideck-php + +# REST API (any language) +npx skills add apideck/api-skills --skill apideck-rest + +# Best practices (recommended with any SDK skill) +npx skills add apideck/api-skills --skill apideck-best-practices + +# API contract testing +npx skills add apideck/api-skills --skill apideck-portman + +# Code generation from OpenAPI specs +npx skills add apideck/api-skills --skill apideck-codegen + +# Connector coverage checking +npx skills add apideck/api-skills --skill apideck-connector-coverage + +# Migration from direct integrations +npx skills add apideck/api-skills --skill apideck-migration +``` + +Add `--global` to install globally across all projects. + +## Skills + +### SDK Skills + +| Skill | Language | Package | +|-------|----------|---------| +| [apideck-node](skills/apideck-node/) | TypeScript / Node.js | `@apideck/unify` | +| [apideck-python](skills/apideck-python/) | Python | `apideck-unify` | +| [apideck-dotnet](skills/apideck-dotnet/) | C# / .NET | `ApideckUnifySdk` | +| [apideck-java](skills/apideck-java/) | Java | `com.apideck:unify` | +| [apideck-go](skills/apideck-go/) | Go | `github.com/apideck-libraries/sdk-go` | +| [apideck-php](skills/apideck-php/) | PHP | `apideck-libraries/sdk-php` | +| [apideck-rest](skills/apideck-rest/) | Any (HTTP) | Direct REST API calls | + +### Integration Skills + +| Skill | Description | +|-------|-------------| +| [apideck-best-practices](skills/apideck-best-practices/) | Architecture patterns, authentication, pagination, error handling, Vault, webhooks, and common pitfalls | +| [apideck-portman](skills/apideck-portman/) | API contract testing with Portman — generate Postman collections with tests from OpenAPI specs | +| [apideck-codegen](skills/apideck-codegen/) | Generate typed clients from OpenAPI specs using openapi-generator, Speakeasy, or Postman import | +| [apideck-connector-coverage](skills/apideck-connector-coverage/) | Check connector API coverage before building — verify which operations each connector supports | +| [apideck-migration](skills/apideck-migration/) | Migrate from direct Salesforce/HubSpot/QuickBooks/Xero integrations to Apideck's unified layer | + +## IDE Plugins + +Pre-configured plugins with skills and slash commands: + +| Provider | Path | +|----------|------| +| Claude Code | [providers/claude/plugin/](providers/claude/plugin/) | +| Cursor | [providers/cursor/plugin/](providers/cursor/plugin/) | + +### Slash Commands + +| Command | Description | +|---------|-------------| +| `/test-connection` | Verify an Apideck connection works by making a test API call | +| `/list-connectors` | Show available connectors and their capabilities for a unified API | +| `/portman-init` | Generate a Portman config for API contract testing against an Apideck spec | + +## Skill Sync + +Skills are synced to provider plugin directories using the sync script: + +```bash +node skills/sync.js # Sync all (fetch spec info + copy skills) +node skills/sync.js --skills-only # Only sync skills to provider dirs +node skills/sync.js --specs-only # Only fetch OpenAPI spec info +``` + +## LLM Integration + +This repository includes [`llms.txt`](llms.txt) following the emerging standard for making content accessible to LLMs. It provides a curated index of all Apideck documentation, API references, SDKs, and developer tools with integration guidelines. + +## Prerequisites + +Set your API key as an environment variable: + +```bash +export APIDECK_API_KEY="your-api-key" +``` + +You also need your **App ID** and **Consumer ID** from the [Apideck dashboard](https://app.apideck.com). + +## What's Covered + +- **Accounting API** - Invoices, bills, payments, ledger accounts, journal entries, tax rates, reports +- **CRM API** - Contacts, companies, leads, opportunities, activities, pipelines +- **HRIS API** - Employees, departments, payrolls, time-off requests +- **File Storage API** - Files, folders, drives, shared links, upload sessions +- **ATS API** - Jobs, applicants, applications +- **Vault API** - Connection management, OAuth flows, custom field mapping +- **Vault JS** - Embeddable modal UI for user-managed integrations +- **Webhook API** - Event subscriptions and real-time notifications +- **Portman** - API contract testing from OpenAPI specs + +## Developer Tools + +- [API Explorer](https://developers.apideck.com/api-explorer) - Test endpoints in the browser with a JWT token +- [OpenAPI Specs](https://specs.apideck.com) - Machine-readable specs for all unified APIs +- [Portman](https://github.com/apideck-libraries/portman) - API contract testing from OpenAPI specs + +## License + +Apache-2.0 diff --git a/llms.txt b/llms.txt new file mode 100644 index 0000000..324c6c4 --- /dev/null +++ b/llms.txt @@ -0,0 +1,77 @@ +# Apideck + +> Apideck provides unified APIs to integrate with 200+ third-party services across accounting, CRM, HRIS, file storage, ATS, and more through a single integration layer. + +## Instructions for Large Language Model Agents + +- Always use the official Apideck SDK for the user's language. Do not make raw HTTP calls when an SDK is available. +- Always pass `apiKey`, `appId`, and `consumerId` when initializing any Apideck SDK client. +- Use `serviceId` to specify which downstream connector to use (e.g., `salesforce`, `quickbooks`, `xero`). When a consumer has multiple connections for the same API, `serviceId` is required. +- Never hardcode API keys in source code. Use environment variables or a secrets manager. +- Use cursor-based pagination to iterate large result sets. Do not fetch all records at once. +- 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. +- Use `pass_through` for connector-specific fields not covered by the unified model. +- Use Apideck Vault and Vault JS for end-user connection management. Session creation must happen server-side to prevent token leakage. +- Use Portman for API contract testing against Apideck's OpenAPI specs. +- Apideck's OpenAPI specs are published at `https://specs.apideck.com/{api-name}.yml`. + +## Docs + +- [Getting Started](https://developers.apideck.com/guides/getting-started): Setup guide for the Apideck platform +- [Authentication](https://developers.apideck.com/guides/authentication): API authentication with Bearer token, App ID, and Consumer ID headers +- [Pagination](https://developers.apideck.com/guides/pagination): Cursor-based pagination across all unified APIs +- [Error Handling](https://developers.apideck.com/errors): Error response format and status codes +- [Rate Limits](https://developers.apideck.com/guides/unified-rate-limits): Downstream rate limit headers and handling +- [Raw Mode](https://developers.apideck.com/guides/using-raw-mode): Access unmodified downstream responses +- [Vault Guide](https://developers.apideck.com/guides/vault): Embeddable connection management UI +- [Logs](https://developers.apideck.com/guides/api-logs): In-app API call logs for debugging and monitoring, also available via the Vault API +- [Webhooks Guide](https://developers.apideck.com/guides/webhooks): Event subscriptions and real-time notifications +- [Custom Field Mapping](https://developers.apideck.com/guides/custom-mapping): Extend the unified model with connector-specific fields + +## Unified APIs + +- [Accounting API Reference](https://developers.apideck.com/apis/accounting/reference): Invoices, bills, payments, ledger accounts, journal entries, tax rates, balance sheets, P&L +- [CRM API Reference](https://developers.apideck.com/apis/crm/reference): Contacts, companies, leads, opportunities, activities, pipelines, notes +- [HRIS API Reference](https://developers.apideck.com/apis/hris/reference): Employees, departments, payrolls, time-off requests, schedules +- [File Storage API Reference](https://developers.apideck.com/apis/file-storage/reference): Files, folders, drives, shared links, upload sessions +- [ATS API Reference](https://developers.apideck.com/apis/ats/reference): Jobs, applicants, applications +- [E-commerce API Reference](https://developers.apideck.com/apis/ecommerce/reference): Orders, products, customers, stores +- [Issue Tracking API Reference](https://developers.apideck.com/apis/issue-tracking/reference): Collections, tickets, comments, tags +- [SMS API Reference](https://developers.apideck.com/apis/sms/reference): Messages +- [Lead API Reference](https://developers.apideck.com/apis/lead/reference): Lead management + +## Platform APIs + +- [Vault API Reference](https://developers.apideck.com/apis/vault/reference): Connection management, OAuth flows, consumers, custom mappings +- [Webhook API Reference](https://developers.apideck.com/apis/webhook/reference): Webhook subscriptions and event logs +- [Connector API Reference](https://developers.apideck.com/apis/connector/reference): Connector metadata and API resource coverage +- [Proxy API Reference](https://developers.apideck.com/apis/proxy/reference): Pass-through requests to downstream services + +## SDKs + +- [TypeScript SDK (@apideck/unify)](https://www.npmjs.com/package/@apideck/unify): Official Node.js/TypeScript SDK +- [Python SDK (apideck-unify)](https://pypi.org/project/apideck-unify/): Official Python SDK +- [C# SDK (ApideckUnifySdk)](https://www.nuget.org/packages/ApideckUnifySdk): Official .NET SDK +- [Java SDK (com.apideck:unify)](https://central.sonatype.com/artifact/com.apideck/unify): Official Java SDK +- [Go SDK](https://github.com/apideck-libraries/sdk-go): Official Go SDK +- [PHP SDK](https://packagist.org/packages/apideck-libraries/sdk-php): Official PHP SDK +- [Vault JS (@apideck/vault-js)](https://www.npmjs.com/package/@apideck/vault-js): Embeddable connection management modal + +## OpenAPI Specs + +- [CRM Spec](https://specs.apideck.com/crm.yml): CRM API OpenAPI specification +- [Accounting Spec](https://specs.apideck.com/accounting.yml): Accounting API OpenAPI specification +- [HRIS Spec](https://specs.apideck.com/hris.yml): HRIS API OpenAPI specification +- [File Storage Spec](https://specs.apideck.com/file-storage.yml): File Storage API OpenAPI specification +- [ATS Spec](https://specs.apideck.com/ats.yml): ATS API OpenAPI specification +- [Vault Spec](https://specs.apideck.com/vault.yml): Vault API OpenAPI specification +- [Webhook Spec](https://specs.apideck.com/webhook.yml): Webhook API OpenAPI specification + +## Developer Tools + +- [API Explorer](https://developers.apideck.com/api-explorer): Test any unified API endpoint in the browser with a JWT token, no code required. URL format: `https://developers.apideck.com/api-explorer?id={api}&headers={encoded_headers_json}` + +## Testing + +- [Portman](https://github.com/apideck-libraries/portman): Convert OpenAPI specs to Postman collections with contract tests diff --git a/providers/claude/plugin/.claude-plugin/plugin.json b/providers/claude/plugin/.claude-plugin/plugin.json new file mode 100644 index 0000000..505aa24 --- /dev/null +++ b/providers/claude/plugin/.claude-plugin/plugin.json @@ -0,0 +1,13 @@ +{ + "name": "apideck", + "description": "Apideck Unified API development plugin for Claude", + "version": "1.0.0", + "author": { + "name": "Apideck", + "url": "https://apideck.com" + }, + "homepage": "https://developers.apideck.com", + "repository": "https://github.com/apideck/api-skills", + "license": "Apache-2.0", + "keywords": ["apideck", "unified-api", "integrations", "accounting", "crm", "hris", "ats", "file-storage"] +} diff --git a/providers/claude/plugin/commands/list-connectors.md b/providers/claude/plugin/commands/list-connectors.md new file mode 100644 index 0000000..71815c6 --- /dev/null +++ b/providers/claude/plugin/commands/list-connectors.md @@ -0,0 +1,18 @@ +--- +description: List available Apideck connectors and their supported operations for a unified API +argument-hint: [api] (e.g., crm, accounting, hris) +--- + +# List Apideck Connectors + +Show available connectors and their capabilities for a given Apideck unified API: + +1. Accept the unified API name from arguments (e.g., `crm`, `accounting`, `hris`, `file-storage`, `ats`) +2. Use the Vault API to list connections: `vault.connections.list({ api: "" })` +3. Display each connector with: + - Service ID and name + - Connection state (available, callable, added, authorized, invalid) + - Whether it's currently enabled +4. If the user has no connections, suggest opening Vault to set them up +5. For each connected service, note which CRUD operations are supported +6. Include a link to the full connector coverage at https://developers.apideck.com/apis/connector/reference diff --git a/providers/claude/plugin/commands/portman-init.md b/providers/claude/plugin/commands/portman-init.md new file mode 100644 index 0000000..e46bf86 --- /dev/null +++ b/providers/claude/plugin/commands/portman-init.md @@ -0,0 +1,21 @@ +--- +description: Generate a Portman configuration for API contract testing against an Apideck spec +argument-hint: [api] (e.g., crm, accounting, hris) +--- + +# Initialize Portman for Apideck API Testing + +Generate a complete Portman configuration for testing an Apideck unified API: + +1. Accept the API name from arguments (e.g., `crm`, `accounting`, `hris`, `file-storage`, `ats`) +2. Create a `portman-config.json` with: + - Contract tests enabled for all operations (status, content-type, JSON body, schema validation) + - Response time tests at 500ms threshold + - Variable chaining: capture `data.id` from create operations for use in get/update/delete + - Security overwrites for Bearer token from `{{bearerToken}}` env var + - Header overwrites for `x-apideck-app-id`, `x-apideck-consumer-id`, `x-apideck-service-id` + - A variation test for 401 Unauthorized with an invalid token + - An integration test for the primary resource lifecycle (create → get → update → delete) +3. Create a `.env` file template with `PORTMAN_BEARER_TOKEN`, `PORTMAN_APP_ID`, `PORTMAN_CONSUMER_ID`, `PORTMAN_SERVICE_ID` +4. Create a `portman-cli-options.json` pointing to the Apideck spec URL (`https://specs.apideck.com/{api}.yml`) +5. Show the command to run: `portman --cliOptionsFile ./portman-cli-options.json` diff --git a/providers/claude/plugin/commands/test-connection.md b/providers/claude/plugin/commands/test-connection.md new file mode 100644 index 0000000..3f43968 --- /dev/null +++ b/providers/claude/plugin/commands/test-connection.md @@ -0,0 +1,22 @@ +--- +description: Test an Apideck connection by listing resources from a specified API and connector +argument-hint: [api] [service_id] (e.g., crm salesforce) +--- + +# Test Apideck Connection + +Verify that an Apideck connection is working by making a simple list call: + +1. Accept the unified API name (e.g., `crm`, `accounting`, `hris`) and service ID (e.g., `salesforce`, `quickbooks`) from arguments +2. Use the appropriate SDK to make a list call with `limit: 1` to the primary resource for that API: + - `crm` → list contacts + - `accounting` → list invoices + - `hris` → list employees + - `file-storage` → list files + - `ats` → list jobs +3. If successful, show the connection status, service name, and a sample record +4. If it fails, explain the error and provide troubleshooting steps: + - 401: Check API key, App ID, and Consumer ID + - 402: Check API plan limits + - 404: Verify the service ID and that the connection is authorized in Vault +5. Suggest using Vault to manage the connection if authorization is needed diff --git a/providers/claude/plugin/skills/apideck-best-practices/SKILL.md b/providers/claude/plugin/skills/apideck-best-practices/SKILL.md new file mode 100644 index 0000000..91fc2b9 --- /dev/null +++ b/providers/claude/plugin/skills/apideck-best-practices/SKILL.md @@ -0,0 +1,176 @@ +--- +name: apideck-best-practices +description: Best practices for building Apideck integrations. Covers authentication patterns, pagination, error handling, connection management with Vault, webhook setup, and common pitfalls. Use when designing or reviewing any Apideck integration regardless of language. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +The Apideck Unified API base URL is `https://unify.apideck.com`. All API calls must be made server-side to prevent token leakage. + +## Authentication + +Every API call requires three headers: `Authorization: Bearer {API_KEY}`, `x-apideck-app-id`, and `x-apideck-consumer-id`. The `x-apideck-service-id` header specifies which downstream connector to use (e.g., `salesforce`, `quickbooks`, `xero`). When a consumer has multiple connections for the same unified API, `x-apideck-service-id` is required. + +Never hardcode API keys in source code. Always use environment variables or a secrets manager. Never expose API keys to the client/browser. + +## SDK Selection + +Always use the official Apideck SDK for the user's language. Do not make raw HTTP calls when an SDK is available: + +| Language | Package | +|----------|---------| +| TypeScript/Node.js | `@apideck/unify` | +| Python | `apideck-unify` | +| C# / .NET | `ApideckUnifySdk` | +| Java | `com.apideck:unify` | +| Go | `github.com/apideck-libraries/sdk-go` | +| PHP | `apideck-libraries/sdk-php` | + +All SDKs follow the same CRUD pattern: `client.{api}.{resource}.{operation}()`. All SDKs support retry configuration with exponential backoff. + +## Consumer ID Architecture + +The `consumerId` represents your end-user — the person whose third-party connections you're accessing. In multi-tenant SaaS applications: + +- Create one Apideck consumer per customer/tenant +- Store the mapping between your user IDs and Apideck consumer IDs +- Pass the correct `consumerId` per request — never use a shared consumer for all users +- Use [Vault sessions](https://developers.apideck.com/guides/vault) to let each user manage their own connections + +## Connection Management + +Always use [Apideck Vault](https://developers.apideck.com/guides/vault) for managing end-user connections. Never build custom OAuth flows when Vault handles them. + +Use [`@apideck/vault-js`](https://www.npmjs.com/package/@apideck/vault-js) to embed the connection management modal in your frontend. Session creation must always happen server-side: + +1. Server-side: Create a Vault session via `vault.sessions.create()` with the consumer's metadata +2. Client-side: Open the modal with `ApideckVault.open({ token })` +3. Handle `onConnectionChange` callbacks to update your UI when users authorize/modify connections + +Customize the Vault modal appearance via session `theme` properties (logo, colors, vault name) to match your brand. + +## Pagination + +Apideck uses cursor-based pagination across all list endpoints. Always paginate — never assume a single page returns all records. + +- Set `limit` (1-200, default 20) to control page size +- Use the SDK's built-in pagination: `for await...of` (Node.js), `.next()` (Python/Go/.NET), `callAsStream()` (Java), `foreach` generator (PHP) +- Stop when the next cursor is `null` + +For incremental sync, use `filter[updated_since]` with an ISO 8601 timestamp to fetch only records modified since your last sync. + +## Filtering and Field Selection + +Always filter server-side using the `filter` parameter. Never fetch all records and filter client-side — this wastes API units and increases response time. + +Always use the `fields` parameter to request only the columns you need. This reduces response size and improves performance. Example: `fields=id,name,email,updated_at`. + +## Error Handling + +Always handle errors. All SDKs provide typed error classes: + +| HTTP Code | Meaning | Action | +|-----------|---------|--------| +| 400 | Bad Request | Fix request parameters | +| 401 | Unauthorized | Check API key and consumer credentials | +| 402 | Payment Required | API limit reached — upgrade plan or wait | +| 404 | Not Found | Resource does not exist or wrong service ID | +| 422 | Unprocessable | Validation error — check required fields | +| 429 | Rate Limited | Back off and retry (check `x-downstream-ratelimit-reset` header) | +| 5xx | Server Error | Retry with exponential backoff | + +For downstream connector errors, inspect the `detail` and `downstream_errors` fields to get the original error from the third-party service. + +## Pass-Through for Connector-Specific Fields + +When the unified model doesn't cover a connector-specific field, use `pass_through` in the request body: + +```json +{ + "first_name": "John", + "pass_through": [ + { + "service_id": "salesforce", + "operation_id": "contactsAdd", + "extend_object": { "custom_sf_field__c": "value" } + } + ] +} +``` + +Use [custom field mapping](https://developers.apideck.com/guides/custom-mapping) in Vault to let end-users map their connector-specific fields without code changes. + +## Webhooks + +Use Apideck webhooks for real-time notifications instead of polling. Apideck supports both native webhooks (from connectors that support them) and virtual webhooks (polling-based for connectors that don't). + +Always verify webhook signatures using the `x-apideck-signature` header with HMAC-SHA256. Never process unverified webhook payloads. + +Webhook events follow the pattern `{api}.{resource}.{action}` (e.g., `crm.contact.created`, `accounting.invoice.updated`). + +## Logs + +Apideck provides detailed API call logs for every request made through the platform. Logs are available both in the Apideck dashboard and via the API. Use logs to debug failed requests, inspect downstream responses, and monitor integration health. + +Access logs programmatically via the Vault API: `vault.logs.list()`. Each log entry includes the HTTP method, URL, status code, request/response bodies, downstream service, and timestamps. + +Use logs when: + +- Debugging why a specific API call failed — inspect the downstream request and response +- Monitoring integration health — track error rates per connector +- Auditing API usage — review which consumers and services are being called +- Troubleshooting data mapping — compare the unified request with the downstream payload + +## Raw Mode + +Append `raw=true` to any request to include the unmodified downstream response alongside the normalized data. Use this for debugging or when you need connector-specific fields not in the unified model. + +## Testing with Portman + +Use [Portman](https://github.com/apideck-libraries/portman) to generate API contract tests from Apideck's OpenAPI specs. Apideck publishes specs at `https://specs.apideck.com/{api-name}.yml`. See the `apideck-portman` skill for full configuration. + +## Developer Tools + +### API Explorer + +The [Apideck API Explorer](https://developers.apideck.com/api-explorer) lets you test any unified API endpoint directly in the browser without writing code. It accepts a JWT token for authentication and returns live responses. + +The Explorer URL format supports pre-filled headers for quick access: + +``` +https://developers.apideck.com/api-explorer?id={api}&headers={encoded_json} +``` + +Where `headers` is a URL-encoded JSON object with: +- `Authorization`: `Bearer {JWT_TOKEN}` +- `x-apideck-auth-type`: `JWT` +- `x-apideck-app-id`: your app ID +- `x-apideck-consumer-id`: the consumer ID to test with + +Recommend the API Explorer when users want to: + +- Quickly verify a connection works before writing integration code +- Explore available fields and response shapes for a resource +- Debug unexpected API responses by comparing with the Explorer output +- Test filter and sort parameters interactively +- Share pre-configured API calls with teammates via URL + +### OpenAPI Specs + +Apideck publishes OpenAPI 3.x specs for all unified APIs at `https://specs.apideck.com/{api-name}.yml`. Use these for: + +- Generating typed clients with code generators +- Contract testing with Portman +- Importing into Postman, Insomnia, or other API tools +- Understanding the complete request/response schema + +## Common Pitfalls + +- Do not assume all connectors support all operations. Check [connector API coverage](https://developers.apideck.com/apis/connector/reference) before building. +- Do not mix `serviceId` values within a single workflow — stick to one connector per operation chain. +- Do not ignore the `row_version` field on updates — use it for optimistic concurrency when supported. +- Do not build retry logic on top of the SDK — all SDKs handle retries for transient errors automatically. +- Do not store Apideck data permanently — Apideck has zero data retention. Use it as a pass-through layer. diff --git a/providers/claude/plugin/skills/apideck-codegen/SKILL.md b/providers/claude/plugin/skills/apideck-codegen/SKILL.md new file mode 100644 index 0000000..bdd9f1b --- /dev/null +++ b/providers/claude/plugin/skills/apideck-codegen/SKILL.md @@ -0,0 +1,258 @@ +--- +name: apideck-codegen +description: Generate typed API clients from Apideck OpenAPI specs using code generators. Use when the user wants to generate custom SDK clients, typed models, API stubs, or server scaffolding from Apideck's published OpenAPI specifications. Covers openapi-generator, Speakeasy, and Postman import workflows. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +# Apideck Code Generation Skill + +## Overview + +Apideck publishes OpenAPI 3.x specifications for all unified APIs at `https://specs.apideck.com/{api-name}.yml`. These specs can be used with code generators to produce typed clients, models, server stubs, and documentation in any language. + +## IMPORTANT RULES + +- ALWAYS prefer the official Apideck SDKs over generated clients. Code generation is for languages/frameworks not covered by official SDKs, or for custom model generation. +- ALWAYS use the latest spec from `https://specs.apideck.com/{api-name}.yml`. Do not use cached or outdated specs. +- USE the official SDKs for: TypeScript (`@apideck/unify`), Python (`apideck-unify`), C# (`ApideckUnifySdk`), Java (`com.apideck:unify`), Go (`sdk-go`), PHP (`sdk-php`). +- When generating clients, always configure authentication headers: `Authorization`, `x-apideck-app-id`, `x-apideck-consumer-id`. + +## Available OpenAPI Specs + +| API | Spec URL | +|-----|----------| +| Accounting | `https://specs.apideck.com/accounting.yml` | +| CRM | `https://specs.apideck.com/crm.yml` | +| HRIS | `https://specs.apideck.com/hris.yml` | +| File Storage | `https://specs.apideck.com/file-storage.yml` | +| ATS | `https://specs.apideck.com/ats.yml` | +| E-commerce | `https://specs.apideck.com/ecommerce.yml` | +| Issue Tracking | `https://specs.apideck.com/issue-tracking.yml` | +| SMS | `https://specs.apideck.com/sms.yml` | +| Lead | `https://specs.apideck.com/lead.yml` | +| Vault | `https://specs.apideck.com/vault.yml` | +| Webhook | `https://specs.apideck.com/webhook.yml` | +| Connector | `https://specs.apideck.com/connector.yml` | + +All specs are also available on GitHub: `https://github.com/apideck-libraries/openapi-specs` + +## OpenAPI Generator + +### Installation + +```sh +# npm +npm install @openapitools/openapi-generator-cli -g + +# Homebrew +brew install openapi-generator + +# Docker +docker pull openapitools/openapi-generator-cli +``` + +### Generate a Typed Client + +```sh +# TypeScript (Axios) +openapi-generator-cli generate \ + -i https://specs.apideck.com/crm.yml \ + -g typescript-axios \ + -o ./generated/crm-client \ + --additional-properties=npmName=apideck-crm,supportsES6=true,withInterfaces=true + +# Python (urllib3) +openapi-generator-cli generate \ + -i https://specs.apideck.com/accounting.yml \ + -g python \ + -o ./generated/accounting-client \ + --additional-properties=packageName=apideck_accounting + +# Rust +openapi-generator-cli generate \ + -i https://specs.apideck.com/crm.yml \ + -g rust \ + -o ./generated/crm-client \ + --additional-properties=packageName=apideck_crm + +# Swift +openapi-generator-cli generate \ + -i https://specs.apideck.com/hris.yml \ + -g swift5 \ + -o ./generated/hris-client \ + --additional-properties=projectName=ApideckHRIS + +# Kotlin +openapi-generator-cli generate \ + -i https://specs.apideck.com/crm.yml \ + -g kotlin \ + -o ./generated/crm-client \ + --additional-properties=groupId=com.apideck,artifactId=crm-client + +# Dart +openapi-generator-cli generate \ + -i https://specs.apideck.com/crm.yml \ + -g dart \ + -o ./generated/crm-client +``` + +### Generate Models Only + +When you only need typed data models without the API client: + +```sh +openapi-generator-cli generate \ + -i https://specs.apideck.com/crm.yml \ + -g typescript-axios \ + -o ./generated/crm-models \ + --global-property=models + +openapi-generator-cli generate \ + -i https://specs.apideck.com/accounting.yml \ + -g python \ + -o ./generated/accounting-models \ + --global-property=models +``` + +### Generate API Documentation + +```sh +# HTML docs +openapi-generator-cli generate \ + -i https://specs.apideck.com/crm.yml \ + -g html2 \ + -o ./docs/crm + +# Markdown docs +openapi-generator-cli generate \ + -i https://specs.apideck.com/crm.yml \ + -g markdown \ + -o ./docs/crm +``` + +### Available Generators + +Run `openapi-generator-cli list` for all generators. Common choices: + +| Language | Client Generator | Notes | +|----------|-----------------|-------| +| TypeScript | `typescript-axios`, `typescript-fetch`, `typescript-node` | `typescript-axios` recommended | +| Python | `python`, `python-pydantic-v1` | `python` uses urllib3 | +| Rust | `rust` | Uses reqwest | +| Swift | `swift5`, `swift6` | Native URLSession | +| Kotlin | `kotlin`, `kotlin-server` | Uses OkHttp | +| Dart | `dart`, `dart-dio` | `dart-dio` for Flutter | +| Ruby | `ruby` | Uses Faraday | +| Elixir | `elixir` | Uses Tesla | +| C++ | `cpp-restsdk` | Uses cpprestsdk | + +### Configuration File + +For repeatable generation, use a config file: + +```yaml +# openapitools.json +{ + "$schema": "node_modules/@openapitools/openapi-generator-cli/config.schema.json", + "spaces": 2, + "generator-cli": { + "version": "7.0.0", + "generators": { + "crm-ts": { + "generatorName": "typescript-axios", + "inputSpec": "https://specs.apideck.com/crm.yml", + "output": "./generated/crm", + "additionalProperties": { + "npmName": "apideck-crm", + "supportsES6": true, + "withInterfaces": true + } + }, + "accounting-py": { + "generatorName": "python", + "inputSpec": "https://specs.apideck.com/accounting.yml", + "output": "./generated/accounting", + "additionalProperties": { + "packageName": "apideck_accounting" + } + } + } + } +} +``` + +Then run: `openapi-generator-cli generate` + +## Speakeasy + +[Speakeasy](https://www.speakeasy.com) is the generator Apideck uses for their official SDKs. + +```sh +# Install +brew install speakeasy-api/homebrew-tap/speakeasy + +# Generate TypeScript SDK +speakeasy generate sdk \ + -s https://specs.apideck.com/crm.yml \ + -t typescript \ + -o ./generated/crm-sdk + +# Generate Python SDK +speakeasy generate sdk \ + -s https://specs.apideck.com/accounting.yml \ + -t python \ + -o ./generated/accounting-sdk + +# Generate Go SDK +speakeasy generate sdk \ + -s https://specs.apideck.com/hris.yml \ + -t go \ + -o ./generated/hris-sdk +``` + +## Postman Import + +Import Apideck specs directly into Postman for manual testing: + +```sh +# Using Portman (recommended — adds contract tests) +npx @apideck/portman -u https://specs.apideck.com/crm.yml -o ./crm.postman.json + +# Direct Postman import +# 1. Open Postman → Import → Link +# 2. Paste: https://specs.apideck.com/crm.yml +# 3. Postman auto-converts the OpenAPI spec to a collection +``` + +## Filtering Specs + +To generate a client for only a subset of endpoints, use `oas-format-filter` (used internally by Portman): + +```sh +npm install -g @apideck/oas-format-filter + +# Filter to only CRM contacts endpoints +oas-format-filter \ + --input https://specs.apideck.com/crm.yml \ + --output ./filtered-crm.yml \ + --filter '{"paths": ["/crm/contacts*"]}' +``` + +Then generate from the filtered spec. + +## Authentication Configuration + +When configuring generated clients, set these headers on every request: + +``` +Authorization: Bearer {APIDECK_API_KEY} +x-apideck-app-id: {APP_ID} +x-apideck-consumer-id: {CONSUMER_ID} +x-apideck-service-id: {SERVICE_ID} (optional, but recommended) +``` + +Base URL: `https://unify.apideck.com` diff --git a/providers/claude/plugin/skills/apideck-connector-coverage/SKILL.md b/providers/claude/plugin/skills/apideck-connector-coverage/SKILL.md new file mode 100644 index 0000000..d691b75 --- /dev/null +++ b/providers/claude/plugin/skills/apideck-connector-coverage/SKILL.md @@ -0,0 +1,185 @@ +--- +name: apideck-connector-coverage +description: Check Apideck connector API coverage before building integrations. Use when determining which operations a connector supports, comparing connector capabilities, or diagnosing why an API call fails with a specific connector. Teaches agents to query the Connector API for real-time coverage data. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +# Apideck Connector Coverage Skill + +## Overview + +Not all Apideck connectors support all operations. Before building an integration, always check connector coverage to avoid runtime errors. The Connector API provides real-time metadata about which operations each connector supports. + +## IMPORTANT RULES + +- ALWAYS check connector coverage before recommending an integration approach. Never assume a connector supports an operation. +- If an operation is not supported, suggest alternatives: a different connector, pass-through to the raw API, or a workaround using supported operations. +- USE the Connector API to verify coverage programmatically. Do not rely on hardcoded lists. +- When a user reports a `501 Not Implemented` or `UnsupportedOperationError`, check coverage first. + +## Checking Coverage + +### Using the SDK (TypeScript) + +```typescript +import { Apideck } from "@apideck/unify"; + +const apideck = new Apideck({ + apiKey: process.env["APIDECK_API_KEY"] ?? "", + appId: "your-app-id", + consumerId: "your-consumer-id", +}); + +// List all connectors for an API +const { data } = await apideck.connector.connectorResources.get({ + id: "crm", + resourceId: "contacts", +}); +// Returns coverage per connector: which operations are supported + +// Get specific connector details +const { data: connector } = await apideck.connector.connectors.get({ + id: "salesforce", +}); +// Returns: name, status, auth_type, supported_resources, supported_events +``` + +### Using the REST API + +```bash +# List connectors for a unified API +curl 'https://unify.apideck.com/connector/connectors?filter[unified_api]=crm' \ + -H 'Authorization: Bearer {API_KEY}' \ + -H 'x-apideck-app-id: {APP_ID}' + +# Get connector details including supported resources +curl 'https://unify.apideck.com/connector/connectors/salesforce' \ + -H 'Authorization: Bearer {API_KEY}' \ + -H 'x-apideck-app-id: {APP_ID}' + +# Get API resource coverage (which connectors support what) +curl 'https://unify.apideck.com/connector/apis/crm/resources/contacts' \ + -H 'Authorization: Bearer {API_KEY}' \ + -H 'x-apideck-app-id: {APP_ID}' +``` + +### Using the Vault API + +Check which connections a consumer has and their state: + +```typescript +const { data } = await apideck.vault.connections.list({ + api: "crm", +}); + +for (const conn of data) { + console.log(`${conn.serviceId}: ${conn.state} (enabled: ${conn.enabled})`); + // state: available | callable | added | authorized | invalid +} +``` + +## Coverage States + +Each operation on a connector has a coverage status: + +| Status | Meaning | +|--------|---------| +| `supported` | Fully implemented and tested | +| `beta` | Implemented but may have edge cases | +| `not_supported` | Not available for this connector | + +## Common Coverage Patterns + +### Accounting API + +| Operation | QuickBooks | Xero | NetSuite | Sage Intacct | FreshBooks | +|-----------|-----------|------|----------|--------------|------------| +| Invoices CRUD | Full | Full | Full | Full | Full | +| Bills CRUD | Full | Full | Full | Full | Partial | +| Payments | Full | Full | Full | Full | Full | +| Journal Entries | Full | Full | Full | Full | Limited | +| Balance Sheet | Full | Full | Full | Full | No | +| Tax Rates (read) | Full | Full | Full | Full | Full | + +### CRM API + +| Operation | Salesforce | HubSpot | Pipedrive | Zoho CRM | Close | +|-----------|-----------|---------|-----------|----------|-------| +| Contacts CRUD | Full | Full | Full | Full | Full | +| Companies CRUD | Full | Full | Full | Full | Full | +| Leads CRUD | Full | Full | Full | Full | Full | +| Opportunities | Full | Full | Full | Full | Full | +| Activities | Full | Full | Full | Full | Partial | +| Pipelines (read) | Full | Full | Full | Full | Full | + +### HRIS API + +| Operation | BambooHR | Workday | Personio | Gusto | Rippling | +|-----------|---------|---------|----------|-------|----------| +| Employees CRUD | Full | Full | Full | Full | Full | +| Departments | Full | Full | Full | Full | Full | +| Payrolls (read) | Full | Partial | Partial | Full | Full | +| Time-Off | Full | Full | Full | Full | Full | + +> These tables are approximate. Always verify with the Connector API for real-time accuracy. + +## Handling Unsupported Operations + +When an operation isn't supported for a connector: + +### 1. Use Pass-Through (Proxy API) + +Make direct calls to the downstream API through Apideck's proxy: + +```typescript +// Direct pass-through to the downstream API +const response = await fetch("https://unify.apideck.com/proxy", { + method: "POST", + headers: { + Authorization: `Bearer ${apiKey}`, + "x-apideck-app-id": appId, + "x-apideck-consumer-id": consumerId, + "x-apideck-service-id": "salesforce", + "x-apideck-downstream-url": "https://api.salesforce.com/services/data/v59.0/sobjects/CustomObject__c", + "x-apideck-downstream-method": "GET", + "Content-Type": "application/json", + }, +}); +``` + +### 2. Check for Alternative Resources + +Some connectors map the same data to different unified resources. For example, a "deal" in Pipedrive maps to "opportunities" in the unified CRM API. + +### 3. Suggest a Different Connector + +If the user's chosen connector doesn't support an operation, suggest alternatives that do. Use the Connector API to find connectors that support the specific operation. + +### 4. Feature Request + +If a critical operation is missing, users can request it at https://github.com/apideck-libraries or through the Apideck dashboard. + +## Connector Authentication Types + +| Auth Type | Description | Connectors | +|-----------|-------------|------------| +| `oauth2` | OAuth 2.0 flow (managed by Vault) | Most cloud SaaS (Salesforce, HubSpot, QuickBooks Online, etc.) | +| `apiKey` | API key authentication | Some self-hosted or simpler services | +| `basic` | Username/password | Legacy systems, on-premise | +| `custom` | Connector-specific auth | Varies | + +Vault handles all OAuth flows. Users authorize via the Vault modal — you never need to implement OAuth yourself. + +## Debugging Coverage Issues + +When an API call fails: + +1. **Check the error type** — `UnsupportedOperationError` or `501` means the operation isn't implemented +2. **Verify connection state** — Use `vault.connections.list()` to check the connection is `authorized` +3. **Check connector coverage** — Use the Connector API to verify the operation is supported +4. **Check field support** — Some connectors support an operation but not all fields. Missing fields return `null` +5. **Use raw mode** — Add `raw=true` to see the downstream response for debugging diff --git a/providers/claude/plugin/skills/apideck-dotnet/SKILL.md b/providers/claude/plugin/skills/apideck-dotnet/SKILL.md new file mode 100644 index 0000000..de4aba5 --- /dev/null +++ b/providers/claude/plugin/skills/apideck-dotnet/SKILL.md @@ -0,0 +1,215 @@ +--- +name: apideck-dotnet +description: Apideck Unified API integration patterns for C# and .NET. 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 using .NET. Covers the ApideckUnifySdk NuGet package, authentication, CRUD operations, pagination, error handling, and Vault connection management. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +# Apideck .NET SDK Skill + +## Overview + +The [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 .NET SDK (`ApideckUnifySdk`) provides typed clients for all unified APIs. + +## Installation + +```sh +dotnet add package ApideckUnifySdk +``` + +## IMPORTANT RULES + +- ALWAYS use the `ApideckUnifySdk` NuGet package. DO NOT make raw `HttpClient` calls to the Apideck API. +- ALWAYS pass `apiKey`, `appId`, and `consumerId` when initializing the client. +- USE `ServiceId` to specify which downstream connector to use (e.g., `"salesforce"`, `"quickbooks"`). +- USE async/await for all API calls — all operations return `Task`. +- ALWAYS handle errors with try/catch using `BaseException` as the base class. +- DO NOT store API keys in source code. Use environment variables or a secrets manager. + +## Quick Start + +```csharp +using ApideckUnifySdk; +using ApideckUnifySdk.Models.Requests; + +var sdk = new Apideck( + consumerId: "your-consumer-id", + appId: "your-app-id", + apiKey: Environment.GetEnvironmentVariable("APIDECK_API_KEY") ?? "" +); + +var res = await sdk.Crm.Contacts.ListAsync(new CrmContactsAllRequest { + ServiceId = "salesforce", + Limit = 20, +}); + +while (res != null) +{ + foreach (var contact in res.GetContactsResponse?.Data ?? []) + { + Console.WriteLine($"{contact.Name} - {contact.Emails?.FirstOrDefault()?.Email}"); + } + res = await res.Next!(); +} +``` + +## SDK Patterns + +### Client Setup + +```csharp +using ApideckUnifySdk; + +var sdk = new Apideck( + consumerId: "your-consumer-id", + appId: "your-app-id", + apiKey: Environment.GetEnvironmentVariable("APIDECK_API_KEY") ?? "" +); +``` + +### CRUD Operations + +All resources follow the pattern: `sdk.{Api}.{Resource}.{Operation}Async(request)`. + +```csharp +using ApideckUnifySdk; +using ApideckUnifySdk.Models.Requests; +using ApideckUnifySdk.Models.Components; + +// LIST +var listRes = await sdk.Crm.Contacts.ListAsync(new CrmContactsAllRequest { + ServiceId = "salesforce", + Limit = 20, + Filter = new ContactsFilter { Email = "john@example.com" }, + Sort = new ContactsSort { + By = ContactsSortBy.UpdatedAt, + Direction = SortDirection.Desc, + }, +}); + +// CREATE +var createRes = await sdk.Crm.Contacts.CreateAsync(new CrmContactsAddRequest { + ServiceId = "salesforce", + Contact = new ContactInput { + FirstName = "John", + LastName = "Doe", + Emails = new List { + new Email { EmailAddress = "john@example.com", Type = EmailType.Primary }, + }, + PhoneNumbers = new List { + new PhoneNumber { Number = "+1234567890", Type = PhoneNumberType.Mobile }, + }, + }, +}); +Console.WriteLine(createRes.CreateContactResponse?.Data?.Id); + +// GET +var getRes = await sdk.Crm.Contacts.GetAsync(new CrmContactsOneRequest { + Id = "contact_123", + ServiceId = "salesforce", +}); + +// UPDATE +var updateRes = await sdk.Crm.Contacts.UpdateAsync(new CrmContactsUpdateRequest { + Id = "contact_123", + ServiceId = "salesforce", + Contact = new ContactInput { FirstName = "Jane" }, +}); + +// DELETE +await sdk.Crm.Contacts.DeleteAsync(new CrmContactsDeleteRequest { + Id = "contact_123", + ServiceId = "salesforce", +}); +``` + +### Pagination + +Use the `Next` method on the response. Returns `null` when no more pages: + +```csharp +var res = await sdk.Accounting.Invoices.ListAsync(new AccountingInvoicesAllRequest { + ServiceId = "quickbooks", + Limit = 50, +}); + +while (res != null) +{ + foreach (var invoice in res.GetInvoicesResponse?.Data ?? []) + { + Console.WriteLine($"{invoice.Number}: {invoice.Total}"); + } + res = await res.Next!(); +} +``` + +### Error Handling + +```csharp +using ApideckUnifySdk.Models.Errors; + +try +{ + var res = await sdk.Crm.Contacts.GetAsync(new CrmContactsOneRequest { Id = "invalid" }); +} +catch (BadRequestResponse e) +{ + Console.Error.WriteLine($"Bad request: {e.Message}"); +} +catch (UnauthorizedResponse e) +{ + Console.Error.WriteLine("Invalid API key or missing credentials"); +} +catch (NotFoundResponse e) +{ + Console.Error.WriteLine("Record not found"); +} +catch (PaymentRequiredResponse e) +{ + Console.Error.WriteLine("API limit reached"); +} +catch (UnprocessableResponse e) +{ + Console.Error.WriteLine($"Validation error: {e.Message}"); +} +catch (BaseException e) +{ + Console.Error.WriteLine($"API error: {e.Message}"); + Console.Error.WriteLine($"Status: {e.Response.StatusCode}"); +} +``` + +### Retry Configuration + +```csharp +var sdk = new Apideck( + retryConfig: new RetryConfig( + strategy: RetryConfig.RetryStrategy.BACKOFF, + backoff: new BackoffStrategy( + initialIntervalMs: 1L, + maxIntervalMs: 50L, + maxElapsedTimeMs: 100L, + exponent: 1.1 + ), + retryConnectionErrors: false + ), + consumerId: "your-consumer-id", + appId: "your-app-id", + apiKey: Environment.GetEnvironmentVariable("APIDECK_API_KEY") ?? "" +); +``` + +## API Namespaces + +| Namespace | Resources | +|-----------|-----------| +| `sdk.Accounting.*` | Invoices, Bills, Payments, Customers, Suppliers, LedgerAccounts, JournalEntries, TaxRates, CreditNotes, PurchaseOrders, BalanceSheet, ProfitAndLoss, and more | +| `sdk.Crm.*` | Contacts, Companies, Leads, Opportunities, Activities, Notes, Pipelines, Users | +| `sdk.Hris.*` | Employees, Companies, Departments, Payrolls, TimeOffRequests | +| `sdk.FileStorage.*` | Files, Folders, Drives, DriveGroups, SharedLinks, UploadSessions | +| `sdk.Ats.*` | Applicants, Applications, Jobs | +| `sdk.Vault.*` | Connections, Consumers, Sessions, CustomMappings, Logs | +| `sdk.Webhook.*` | Webhooks, EventLogs | diff --git a/providers/claude/plugin/skills/apideck-go/SKILL.md b/providers/claude/plugin/skills/apideck-go/SKILL.md new file mode 100644 index 0000000..57db168 --- /dev/null +++ b/providers/claude/plugin/skills/apideck-go/SKILL.md @@ -0,0 +1,255 @@ +--- +name: apideck-go +description: Apideck Unified API integration patterns for Go. 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 using Go. Covers the github.com/apideck-libraries/sdk-go package, authentication, CRUD operations, pagination, error handling, and Vault connection management. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +# Apideck Go SDK Skill + +## Overview + +The [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 Go SDK provides typed clients for all unified APIs. + +## Installation + +```sh +go get github.com/apideck-libraries/sdk-go +``` + +## IMPORTANT RULES + +- ALWAYS use the `github.com/apideck-libraries/sdk-go` SDK. DO NOT make raw `net/http` calls to the Apideck API. +- ALWAYS pass security, app ID, and consumer ID via functional options when creating the client. +- USE `ServiceID` on requests to specify which downstream connector to use. +- ALWAYS check returned `error` values (idiomatic Go error handling). +- USE `sdkgo.Pointer()` helper for optional fields. +- DO NOT store API keys in source code. Use environment variables. + +## Quick Start + +```go +package main + +import ( + "context" + "fmt" + "log" + "os" + + sdkgo "github.com/apideck-libraries/sdk-go" + "github.com/apideck-libraries/sdk-go/models/components" + "github.com/apideck-libraries/sdk-go/models/operations" +) + +func main() { + ctx := context.Background() + + s := sdkgo.New( + sdkgo.WithConsumerID("your-consumer-id"), + sdkgo.WithAppID("your-app-id"), + sdkgo.WithSecurity(os.Getenv("APIDECK_API_KEY")), + ) + + res, err := s.Crm.Contacts.List(ctx, operations.CrmContactsAllRequest{ + ServiceID: sdkgo.Pointer("salesforce"), + Limit: sdkgo.Pointer(int64(20)), + }) + if err != nil { + log.Fatal(err) + } + + for _, contact := range res.GetContactsResponse.Data { + fmt.Println(contact.Name) + } +} +``` + +## SDK Patterns + +### Client Setup + +```go +import sdkgo "github.com/apideck-libraries/sdk-go" + +s := sdkgo.New( + sdkgo.WithConsumerID("your-consumer-id"), + sdkgo.WithAppID("your-app-id"), + sdkgo.WithSecurity(os.Getenv("APIDECK_API_KEY")), +) +``` + +### CRUD Operations + +All resources follow the pattern: `s.{Api}.{Resource}.{Operation}(ctx, request)`, returning `(response, error)`. + +```go +import ( + sdkgo "github.com/apideck-libraries/sdk-go" + "github.com/apideck-libraries/sdk-go/models/components" + "github.com/apideck-libraries/sdk-go/models/operations" +) + +ctx := context.Background() + +// LIST +res, err := s.Crm.Contacts.List(ctx, operations.CrmContactsAllRequest{ + ServiceID: sdkgo.Pointer("salesforce"), + Limit: sdkgo.Pointer(int64(20)), + Filter: &components.ContactsFilter{ + Email: sdkgo.Pointer("john@example.com"), + }, + Sort: &components.ContactsSort{ + By: (*components.ContactsSortBy)(sdkgo.Pointer("updated_at")), + Direction: (*components.SortDirection)(sdkgo.Pointer("desc")), + }, +}) + +// CREATE +res, err := s.Crm.Contacts.Create(ctx, operations.CrmContactsAddRequest{ + ServiceID: sdkgo.Pointer("salesforce"), + Contact: components.ContactInput{ + FirstName: sdkgo.Pointer("John"), + LastName: sdkgo.Pointer("Doe"), + Emails: []components.Email{ + {Email: sdkgo.Pointer("john@example.com"), Type: (*components.EmailType)(sdkgo.Pointer("primary"))}, + }, + PhoneNumbers: []components.PhoneNumber{ + {Number: sdkgo.Pointer("+1234567890"), Type: (*components.PhoneNumberType)(sdkgo.Pointer("mobile"))}, + }, + }, +}) + +// GET +res, err := s.Crm.Contacts.Get(ctx, operations.CrmContactsOneRequest{ + ID: "contact_123", + ServiceID: sdkgo.Pointer("salesforce"), +}) + +// UPDATE +res, err := s.Crm.Contacts.Update(ctx, operations.CrmContactsUpdateRequest{ + ID: "contact_123", + ServiceID: sdkgo.Pointer("salesforce"), + Contact: components.ContactInput{ + FirstName: sdkgo.Pointer("Jane"), + }, +}) + +// DELETE +res, err := s.Crm.Contacts.Delete(ctx, operations.CrmContactsDeleteRequest{ + ID: "contact_123", + ServiceID: sdkgo.Pointer("salesforce"), +}) +``` + +### Pagination + +Use the `Next()` method on the response. Returns `nil` when no more pages: + +```go +res, err := s.Accounting.Invoices.List(ctx, operations.AccountingInvoicesAllRequest{ + ServiceID: sdkgo.Pointer("quickbooks"), + Limit: sdkgo.Pointer(int64(50)), +}) +if err != nil { + log.Fatal(err) +} + +for { + for _, invoice := range res.GetInvoicesResponse.Data { + fmt.Printf("%s: %v\n", *invoice.Number, *invoice.Total) + } + + res, err = res.Next() + if err != nil { + log.Fatal(err) + } + if res == nil { + break + } +} +``` + +### Error Handling + +```go +import ( + "errors" + "github.com/apideck-libraries/sdk-go/models/apierrors" +) + +res, err := s.Crm.Contacts.Get(ctx, req) +if err != nil { + var badReq *apierrors.BadRequestResponse + var unauthorized *apierrors.UnauthorizedResponse + var notFound *apierrors.NotFoundResponse + var paymentReq *apierrors.PaymentRequiredResponse + var unprocessable *apierrors.UnprocessableResponse + + switch { + case errors.As(err, &badReq): + log.Printf("Bad request: %s", badReq.Error()) + case errors.As(err, &unauthorized): + log.Printf("Unauthorized: %s", unauthorized.Error()) + case errors.As(err, ¬Found): + log.Printf("Not found: %s", notFound.Error()) + case errors.As(err, &paymentReq): + log.Printf("Payment required: %s", paymentReq.Error()) + case errors.As(err, &unprocessable): + log.Printf("Unprocessable: %s", unprocessable.Error()) + default: + var apiErr *apierrors.APIError + if errors.As(err, &apiErr) { + log.Printf("API error %d: %s", apiErr.StatusCode, apiErr.Error()) + } else { + log.Fatal(err) + } + } +} +``` + +### Retry Configuration + +```go +import "github.com/apideck-libraries/sdk-go/retry" + +// Global +s := sdkgo.New( + sdkgo.WithRetryConfig(retry.Config{ + Strategy: "backoff", + Backoff: &retry.BackoffStrategy{ + InitialInterval: 1, + MaxInterval: 50, + Exponent: 1.1, + MaxElapsedTime: 100, + }, + RetryConnectionErrors: false, + }), + sdkgo.WithConsumerID("your-consumer-id"), + sdkgo.WithAppID("your-app-id"), + sdkgo.WithSecurity(os.Getenv("APIDECK_API_KEY")), +) + +// Per-operation +res, err := s.Crm.Contacts.List(ctx, req, + operations.WithRetries(retry.Config{ + Strategy: "backoff", + Backoff: &retry.BackoffStrategy{InitialInterval: 1, MaxInterval: 50, Exponent: 1.1, MaxElapsedTime: 100}, + }), +) +``` + +## API Namespaces + +| Namespace | Resources | +|-----------|-----------| +| `s.Accounting.*` | Invoices, Bills, Payments, Customers, Suppliers, LedgerAccounts, JournalEntries, TaxRates, CreditNotes, PurchaseOrders, BalanceSheet, ProfitAndLoss, and more | +| `s.Crm.*` | Contacts, Companies, Leads, Opportunities, Activities, Notes, Pipelines, Users | +| `s.Hris.*` | Employees, Companies, Departments, Payrolls, TimeOffRequests | +| `s.FileStorage.*` | Files, Folders, Drives, DriveGroups, SharedLinks, UploadSessions | +| `s.Ats.*` | Applicants, Applications, Jobs | +| `s.Vault.*` | Connections, Consumers, Sessions, CustomMappings, Logs | +| `s.Webhook.*` | Webhooks, EventLogs | diff --git a/providers/claude/plugin/skills/apideck-java/SKILL.md b/providers/claude/plugin/skills/apideck-java/SKILL.md new file mode 100644 index 0000000..ad9979f --- /dev/null +++ b/providers/claude/plugin/skills/apideck-java/SKILL.md @@ -0,0 +1,256 @@ +--- +name: apideck-java +description: Apideck Unified API integration patterns for Java. 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 using Java. Covers the com.apideck:unify Maven package, authentication, CRUD operations, pagination, async support, and Vault connection management. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +# Apideck Java SDK Skill + +## Overview + +The [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 Java SDK (`com.apideck:unify`) provides typed clients for all unified APIs. + +## Installation + +**Gradle:** +```groovy +implementation 'com.apideck:unify:0.30.3' +``` + +**Maven:** +```xml + + com.apideck + unify + 0.30.3 + +``` + +Requires JDK 11 or later. + +## IMPORTANT RULES + +- ALWAYS use the `com.apideck:unify` SDK. DO NOT make raw HTTP calls to the Apideck API. +- ALWAYS pass `apiKey`, `appId`, and `consumerId` when building the client. +- USE `serviceId` on requests to specify which downstream connector to use. +- USE the fluent builder pattern for constructing requests. +- ALWAYS handle errors with try/catch using `ApideckError` as the base class. +- DO NOT store API keys in source code. Use environment variables. + +## Quick Start + +```java +import com.apideck.unify.Apideck; +import com.apideck.unify.models.operations.*; + +Apideck sdk = Apideck.builder() + .consumerId("your-consumer-id") + .appId("your-app-id") + .apiKey(System.getenv("APIDECK_API_KEY")) + .build(); + +sdk.crm().contacts().list() + .serviceId("salesforce") + .limit(20) + .callAsStream() + .forEach(page -> { + page.getContactsResponse().ifPresent(res -> + res.getData().forEach(contact -> + System.out.println(contact.getName()) + ) + ); + }); +``` + +## SDK Patterns + +### Client Setup + +```java +import com.apideck.unify.Apideck; + +Apideck sdk = Apideck.builder() + .consumerId("your-consumer-id") + .appId("your-app-id") + .apiKey(System.getenv("APIDECK_API_KEY")) + .build(); +``` + +Async client: + +```java +import com.apideck.unify.AsyncApideck; + +AsyncApideck asyncSdk = Apideck.builder() + .consumerId("your-consumer-id") + .appId("your-app-id") + .apiKey(System.getenv("APIDECK_API_KEY")) + .build() + .async(); +``` + +### CRUD Operations + +Uses a fluent builder pattern: `sdk.{category}().{resource}().{operation}()`. + +```java +import com.apideck.unify.models.components.*; +import com.apideck.unify.models.operations.*; + +// LIST +sdk.crm().contacts().list() + .serviceId("salesforce") + .limit(20) + .filter(ContactsFilter.builder().email("john@example.com").build()) + .sort(ContactsSort.builder() + .by(ContactsSortBy.UPDATED_AT) + .direction(SortDirection.DESC) + .build()) + .call(); + +// CREATE +sdk.crm().contacts().create() + .serviceId("salesforce") + .contact(ContactInput.builder() + .firstName("John") + .lastName("Doe") + .emails(List.of(Email.builder() + .email("john@example.com") + .type(EmailType.PRIMARY) + .build())) + .phoneNumbers(List.of(PhoneNumber.builder() + .number("+1234567890") + .type(PhoneNumberType.MOBILE) + .build())) + .build()) + .call(); + +// GET +sdk.crm().contacts().get() + .id("contact_123") + .serviceId("salesforce") + .call(); + +// UPDATE +sdk.crm().contacts().update() + .id("contact_123") + .serviceId("salesforce") + .contact(ContactInput.builder().firstName("Jane").build()) + .call(); + +// DELETE +sdk.crm().contacts().delete() + .id("contact_123") + .serviceId("salesforce") + .call(); +``` + +### Pagination + +Multiple approaches available: + +```java +// Stream (recommended) +sdk.accounting().invoices().list() + .serviceId("quickbooks") + .limit(50) + .callAsStream() + .forEach(page -> { + // handle page + }); + +// Iterable +for (var page : sdk.accounting().invoices().list() + .serviceId("quickbooks") + .limit(50) + .callAsIterable()) { + // handle page +} + +// Reactive Streams (for Project Reactor, RxJava, etc.) +var publisher = sdk.accounting().invoices().list() + .serviceId("quickbooks") + .limit(50) + .callAsPublisher(); +``` + +### Async Support + +Returns `CompletableFuture` for standard operations: + +```java +AsyncApideck asyncSdk = sdk.async(); + +asyncSdk.crm().contacts().list() + .serviceId("salesforce") + .limit(20) + .call() + .thenAccept(res -> { + // handle response + }); +``` + +### Error Handling + +```java +import com.apideck.unify.models.errors.*; + +try { + sdk.crm().contacts().get() + .id("invalid") + .serviceId("salesforce") + .call(); +} catch (BadRequestResponse e) { + System.err.println("Bad request: " + e.message()); +} catch (UnauthorizedResponse e) { + System.err.println("Invalid API key"); +} catch (NotFoundResponse e) { + System.err.println("Record not found"); +} catch (PaymentRequiredResponse e) { + System.err.println("API limit reached"); +} catch (UnprocessableResponse e) { + System.err.println("Validation error: " + e.message()); +} catch (ApideckError e) { + System.err.println("API error " + e.code() + ": " + e.message()); +} +``` + +### Retry Configuration + +```java +import com.apideck.unify.utils.BackoffStrategy; +import com.apideck.unify.utils.RetryConfig; +import java.util.concurrent.TimeUnit; + +Apideck sdk = Apideck.builder() + .retryConfig(RetryConfig.builder() + .backoff(BackoffStrategy.builder() + .initialInterval(1L, TimeUnit.MILLISECONDS) + .maxInterval(50L, TimeUnit.MILLISECONDS) + .maxElapsedTime(1000L, TimeUnit.MILLISECONDS) + .baseFactor(1.1) + .jitterFactor(0.15) + .retryConnectError(false) + .build()) + .build()) + .consumerId("your-consumer-id") + .appId("your-app-id") + .apiKey(System.getenv("APIDECK_API_KEY")) + .build(); +``` + +## API Namespaces + +| Namespace | Resources | +|-----------|-----------| +| `sdk.accounting().*` | invoices, bills, payments, customers, suppliers, ledgerAccounts, journalEntries, taxRates, creditNotes, purchaseOrders, balanceSheet, profitAndLoss, and more | +| `sdk.crm().*` | contacts, companies, leads, opportunities, activities, notes, pipelines, users | +| `sdk.hris().*` | employees, companies, departments, payrolls, timeOffRequests | +| `sdk.fileStorage().*` | files, folders, drives, driveGroups, sharedLinks, uploadSessions | +| `sdk.ats().*` | applicants, applications, jobs | +| `sdk.vault().*` | connections, consumers, sessions, customMappings, logs | +| `sdk.webhook().*` | webhooks, eventLogs | diff --git a/providers/claude/plugin/skills/apideck-migration/SKILL.md b/providers/claude/plugin/skills/apideck-migration/SKILL.md new file mode 100644 index 0000000..efce36d --- /dev/null +++ b/providers/claude/plugin/skills/apideck-migration/SKILL.md @@ -0,0 +1,262 @@ +--- +name: apideck-migration +description: Guide for migrating from direct third-party API integrations to Apideck's unified API. Use when a user wants to replace their existing Salesforce, HubSpot, QuickBooks, Xero, or other direct API integrations with Apideck, or when consolidating multiple integrations into a single unified layer. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +# Apideck Migration Guide Skill + +## Overview + +This skill helps migrate existing direct third-party API integrations (Salesforce, HubSpot, QuickBooks, Xero, etc.) to Apideck's unified API layer. The benefit is replacing N separate integrations with a single Apideck integration that supports 200+ connectors. + +## IMPORTANT RULES + +- ALWAYS check connector coverage before migrating. Not all operations may be supported through the unified API. +- ALWAYS preserve existing data flows and business logic. Migration should be transparent to end-users. +- USE `pass_through` for connector-specific fields that don't map to the unified model. +- USE custom field mapping in Vault for recurring connector-specific fields. +- NEVER delete the existing integration code until the Apideck migration is verified in production. +- RECOMMEND a phased migration: start with read operations, then writes, then webhooks. + +## Migration Strategy + +### Phase 1: Assessment + +1. **Inventory existing integrations** — List all third-party APIs currently in use +2. **Map operations** — For each integration, list the CRUD operations and fields used +3. **Check coverage** — Verify each operation is supported via the Apideck Connector API +4. **Identify gaps** — Note operations that need `pass_through` or Proxy API +5. **Plan consumer mapping** — Decide how your users/tenants map to Apideck consumer IDs + +### Phase 2: Connection Setup + +1. **Create Apideck account** — Get API key and App ID from the dashboard +2. **Enable connectors** — Enable the connectors you need in the Apideck dashboard +3. **Integrate Vault** — Add Vault JS to your frontend for user-managed connections +4. **Create consumers** — Map your existing users to Apideck consumer IDs +5. **Authorize connections** — Have users re-authorize via Vault (OAuth is handled automatically) + +### Phase 3: Read Migration (Low Risk) + +Replace read operations first since they're non-destructive: + +```typescript +// BEFORE: Direct Salesforce API +const contacts = await salesforce.sobjects.Contact.find({ + Email: "john@example.com", +}); + +// AFTER: Apideck Unified API +const { data } = await apideck.crm.contacts.list({ + serviceId: "salesforce", + filter: { email: "john@example.com" }, +}); +``` + +### Phase 4: Write Migration + +Replace create/update/delete operations: + +```typescript +// BEFORE: Direct HubSpot API +const contact = await hubspot.crm.contacts.basicApi.create({ + properties: { + firstname: "John", + lastname: "Doe", + email: "john@example.com", + company: "Acme Corp", + }, +}); + +// AFTER: Apideck Unified API +const { data } = await apideck.crm.contacts.create({ + serviceId: "hubspot", + contact: { + first_name: "John", + last_name: "Doe", + emails: [{ email: "john@example.com", type: "primary" }], + company_name: "Acme Corp", + }, +}); +``` + +### Phase 5: Webhook Migration + +Replace direct webhook handlers with Apideck's unified webhooks: + +```typescript +// BEFORE: Salesforce-specific webhook handler +app.post("/webhooks/salesforce", (req, res) => { + const event = req.body; + if (event.type === "ContactChangeEvent") { + handleContactChange(event); + } +}); + +// AFTER: Apideck unified webhook handler +app.post("/webhooks/apideck", (req, res) => { + const signature = req.headers["x-apideck-signature"]; + if (!verifySignature(req.body, signature, secret)) { + return res.status(401).send("Invalid signature"); + } + + const { event_type, entity_id, service_id } = req.body.payload; + // Works for ALL connectors, not just Salesforce + if (event_type === "crm.contact.updated") { + handleContactChange(entity_id, service_id); + } + res.status(200).send("OK"); +}); +``` + +## Common Migration Patterns + +### CRM: Salesforce to Apideck + +| Salesforce API | Apideck Unified API | +|---------------|---------------------| +| `sobjects.Contact.create()` | `crm.contacts.create({ serviceId: "salesforce" })` | +| `sobjects.Account.find()` | `crm.companies.list({ serviceId: "salesforce" })` | +| `sobjects.Opportunity.update()` | `crm.opportunities.update({ serviceId: "salesforce" })` | +| `sobjects.Lead.create()` | `crm.leads.create({ serviceId: "salesforce" })` | +| `sobjects.Task.create()` | `crm.activities.create({ serviceId: "salesforce" })` | +| Custom fields via `custom_sf_field__c` | `pass_through: [{ service_id: "salesforce", extend_object: { custom_sf_field__c: "value" } }]` | + +### CRM: HubSpot to Apideck + +| HubSpot API | Apideck Unified API | +|-------------|---------------------| +| `crm.contacts.basicApi.create()` | `crm.contacts.create({ serviceId: "hubspot" })` | +| `crm.companies.basicApi.getAll()` | `crm.companies.list({ serviceId: "hubspot" })` | +| `crm.deals.basicApi.create()` | `crm.opportunities.create({ serviceId: "hubspot" })` | +| `crm.contacts.searchApi.doSearch()` | `crm.contacts.list({ serviceId: "hubspot", filter: { ... } })` | + +### Accounting: QuickBooks to Apideck + +| QuickBooks API | Apideck Unified API | +|---------------|---------------------| +| `Invoice.create()` | `accounting.invoices.create({ serviceId: "quickbooks" })` | +| `Customer.findAll()` | `accounting.customers.list({ serviceId: "quickbooks" })` | +| `Bill.create()` | `accounting.bills.create({ serviceId: "quickbooks" })` | +| `Payment.create()` | `accounting.payments.create({ serviceId: "quickbooks" })` | +| `JournalEntry.create()` | `accounting.journalEntries.create({ serviceId: "quickbooks" })` | +| `CompanyInfo.get()` | `accounting.companyInfo.get({ serviceId: "quickbooks" })` | + +### Accounting: Xero to Apideck + +| Xero API | Apideck Unified API | +|----------|---------------------| +| `xero.accountingApi.createInvoices()` | `accounting.invoices.create({ serviceId: "xero" })` | +| `xero.accountingApi.getContacts()` | `accounting.customers.list({ serviceId: "xero" })` | +| `xero.accountingApi.createBankTransactions()` | `accounting.payments.create({ serviceId: "xero" })` | +| `xero.accountingApi.getReportBalanceSheet()` | `accounting.balanceSheet.get({ serviceId: "xero" })` | + +### HRIS: BambooHR to Apideck + +| BambooHR API | Apideck Unified API | +|-------------|---------------------| +| `GET /employees/directory` | `hris.employees.list({ serviceId: "bamboohr" })` | +| `POST /employees` | `hris.employees.create({ serviceId: "bamboohr" })` | +| `GET /employees/{id}` | `hris.employees.get({ serviceId: "bamboohr", id })` | +| `PUT /employees/{id}/time_off/request` | `hris.timeOffRequests.create({ serviceId: "bamboohr" })` | + +### File Storage: Google Drive to Apideck + +| Google Drive API | Apideck Unified API | +|-----------------|---------------------| +| `drive.files.list()` | `fileStorage.files.list({ serviceId: "google-drive" })` | +| `drive.files.create()` | `fileStorage.files.create({ serviceId: "google-drive" })` | +| `drive.files.get()` | `fileStorage.files.get({ serviceId: "google-drive" })` | +| `drive.files.export()` | `fileStorage.files.download({ serviceId: "google-drive" })` | +| `drive.permissions.create()` | `fileStorage.sharedLinks.create({ serviceId: "google-drive" })` | + +## Handling Connector-Specific Fields + +### Option 1: Pass-Through (inline) + +For one-off connector-specific fields in request bodies: + +```typescript +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: { + RecordTypeId: "012000000000001", + Custom_Score__c: 85, + }, + }, + ], + }, +}); +``` + +### Option 2: Custom Field Mapping (reusable) + +For fields that are used repeatedly, configure custom mapping in Vault so they appear as part of the unified model: + +```typescript +// Set up custom mapping via Vault API +await apideck.vault.customMappings.update({ + unifiedApi: "crm", + serviceId: "salesforce", + id: "mapping_123", + customMapping: { + value: "$.Custom_Score__c", + }, +}); + +// Now the field appears in custom_fields on every response +const { data } = await apideck.crm.contacts.get({ + id: "contact_123", + serviceId: "salesforce", +}); +// data.custom_fields includes { id: "mapping_123", value: 85 } +``` + +### Option 3: Proxy API (full control) + +For operations not supported by the unified API, use the Proxy to make direct downstream calls while still using Apideck's managed authentication: + +```typescript +const response = await fetch("https://unify.apideck.com/proxy", { + method: "POST", + headers: { + Authorization: `Bearer ${apiKey}`, + "x-apideck-app-id": appId, + "x-apideck-consumer-id": consumerId, + "x-apideck-service-id": "salesforce", + "x-apideck-downstream-url": "/services/data/v59.0/sobjects/CustomObject__c", + "x-apideck-downstream-method": "GET", + "Content-Type": "application/json", + }, +}); +``` + +## Testing the Migration + +1. **Run both integrations in parallel** — Shadow mode: make Apideck calls alongside existing calls and compare responses +2. **Use raw mode** — Add `raw=true` to Apideck calls to compare with the original API response +3. **Contract test with Portman** — Generate tests from OpenAPI specs and run against your staging environment +4. **Test with the API Explorer** — Use the [Apideck API Explorer](https://developers.apideck.com/api-explorer) to verify endpoints interactively +5. **Gradual rollout** — Migrate one connector at a time, starting with the lowest-traffic integration + +## Post-Migration Benefits + +Once migrated to Apideck: + +- **Add new connectors instantly** — Enable a new connector in the dashboard, no code changes needed +- **User self-service** — End-users manage their own connections via Vault +- **Unified webhooks** — One handler for all connectors instead of N separate handlers +- **Unified error handling** — One error format instead of learning each API's error structure +- **Automatic maintenance** — Apideck handles API version changes, deprecations, and auth token refresh diff --git a/providers/claude/plugin/skills/apideck-node/SKILL.md b/providers/claude/plugin/skills/apideck-node/SKILL.md new file mode 100644 index 0000000..b91e83b --- /dev/null +++ b/providers/claude/plugin/skills/apideck-node/SKILL.md @@ -0,0 +1,276 @@ +--- +name: apideck-node +description: 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. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +# Apideck TypeScript SDK Skill + +## Overview + +The [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. + +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 + +```sh +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 + +```typescript +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 + +```typescript +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}()`. + +```typescript +// 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: + +```typescript +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: + +```typescript +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 + +```typescript +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`: + +```typescript +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: + +```typescript +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](references/accounting-api.md) | invoices, bills, payments, customers, suppliers, ledgerAccounts, journalEntries, taxRates, creditNotes, purchaseOrders, balanceSheet, profitAndLoss, and more | +| `apideck.crm.*` | [references/crm-api.md](references/crm-api.md) | contacts, companies, leads, opportunities, activities, notes, pipelines, users | +| `apideck.hris.*` | [references/hris-api.md](references/hris-api.md) | employees, companies, departments, payrolls, timeOffRequests | +| `apideck.fileStorage.*` | [references/file-storage-api.md](references/file-storage-api.md) | files, folders, drives, driveGroups, sharedLinks, uploadSessions | +| `apideck.ats.*` | [references/ats-api.md](references/ats-api.md) | applicants, applications, jobs | +| `apideck.vault.*` | [references/vault-api.md](references/vault-api.md) | connections, connectionSettings, consumers, customMappings, logs, sessions | +| `apideck.webhook.*` | [references/webhook-api.md](references/webhook-api.md) | webhooks, eventLogs | + +## Vault JS (Embeddable UI) + +Use [`@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. + +```typescript +// 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](references/vault-js.md) for full configuration options, theming, React integration, and event callbacks. diff --git a/providers/claude/plugin/skills/apideck-node/references/accounting-api.md b/providers/claude/plugin/skills/apideck-node/references/accounting-api.md new file mode 100644 index 0000000..5fb4ee1 --- /dev/null +++ b/providers/claude/plugin/skills/apideck-node/references/accounting-api.md @@ -0,0 +1,349 @@ +# Accounting API Reference + +Base namespace: `apideck.accounting` + +Supported connectors: QuickBooks, Xero, NetSuite, Exact Online, FreshBooks, Sage Intacct, Sage Business Cloud, MYOB, Wave, Zoho Books, and 20+ more. + +## Invoices + +```typescript +// List invoices +const { data } = await apideck.accounting.invoices.list({ + serviceId: "quickbooks", + limit: 50, + filter: { updated_since: "2024-01-01T00:00:00.000Z" }, + sort: { by: "updated_at", direction: "desc" }, +}); + +// Create invoice +const { data } = await apideck.accounting.invoices.create({ + serviceId: "quickbooks", + invoice: { + type: "standard", + number: "INV-001", + customer: { id: "customer_123", display_name: "Acme Corp" }, + invoice_date: "2024-06-01", + due_date: "2024-07-01", + currency: "USD", + line_items: [ + { + description: "Consulting services", + quantity: 10, + unit_price: 150, + total_amount: 1500, + tax_rate: { id: "tax_rate_1" }, + }, + ], + }, +}); + +// Get invoice by ID +const { data } = await apideck.accounting.invoices.get({ + id: "invoice_123", + serviceId: "quickbooks", +}); + +// Update invoice +const { data } = await apideck.accounting.invoices.update({ + id: "invoice_123", + serviceId: "quickbooks", + invoice: { due_date: "2024-08-01" }, +}); + +// Delete invoice +await apideck.accounting.invoices.delete({ + id: "invoice_123", + serviceId: "quickbooks", +}); +``` + +Key invoice fields: `id`, `number`, `type` (`standard` | `credit`), `customer`, `invoice_date`, `due_date`, `currency`, `line_items[]`, `sub_total`, `total_tax`, `total`, `balance`, `status` (`draft` | `submitted` | `authorised` | `paid` | `voided`), `payment_method`, `tracking_categories[]`, `custom_fields[]`. + +## Bills + +```typescript +// List bills +const { data } = await apideck.accounting.bills.list({ + serviceId: "xero", + filter: { updated_since: "2024-01-01T00:00:00.000Z" }, +}); + +// Create bill +const { data } = await apideck.accounting.bills.create({ + serviceId: "xero", + bill: { + supplier: { id: "supplier_123" }, + bill_date: "2024-06-01", + due_date: "2024-07-01", + currency: "USD", + line_items: [ + { + description: "Office supplies", + quantity: 1, + unit_price: 250, + total_amount: 250, + }, + ], + }, +}); +``` + +## Payments + +```typescript +// List payments +const { data } = await apideck.accounting.payments.list({ + serviceId: "quickbooks", +}); + +// Create payment +const { data } = await apideck.accounting.payments.create({ + serviceId: "quickbooks", + payment: { + customer: { id: "customer_123" }, + total_amount: 1500, + currency: "USD", + payment_method: "credit_card", + reference: "PAY-001", + allocations: [{ id: "invoice_123", type: "invoice", amount: 1500 }], + }, +}); +``` + +## Customers + +```typescript +// List customers +const { data } = await apideck.accounting.customers.list({ + serviceId: "quickbooks", + filter: { company_name: "Acme", email: "billing@acme.com" }, +}); + +// Create customer +const { data } = await apideck.accounting.customers.create({ + serviceId: "quickbooks", + customer: { + display_name: "Acme Corp", + company_name: "Acme Corporation", + emails: [{ email: "billing@acme.com", type: "primary" }], + phone_numbers: [{ number: "+1234567890", type: "primary" }], + addresses: [ + { + type: "primary", + street_1: "123 Main St", + city: "San Francisco", + state: "CA", + postal_code: "94105", + country: "US", + }, + ], + }, +}); +``` + +## Suppliers + +```typescript +const { data } = await apideck.accounting.suppliers.list({ + serviceId: "xero", + filter: { company_name: "Vendor Inc" }, +}); + +const { data } = await apideck.accounting.suppliers.create({ + serviceId: "xero", + supplier: { + display_name: "Vendor Inc", + company_name: "Vendor Incorporated", + emails: [{ email: "ap@vendor.com", type: "primary" }], + }, +}); +``` + +## Ledger Accounts + +```typescript +// List chart of accounts +const { data } = await apideck.accounting.ledgerAccounts.list({ + serviceId: "quickbooks", + filter: { type: "expense" }, +}); + +// Create ledger account +const { data } = await apideck.accounting.ledgerAccounts.create({ + serviceId: "quickbooks", + ledgerAccount: { + display_id: "6000", + name: "Marketing Expenses", + type: "expense", + sub_type: "expense", + currency: "USD", + }, +}); +``` + +Ledger account types: `asset`, `liability`, `equity`, `income`, `expense`, `other`. + +## Journal Entries + +```typescript +const { data } = await apideck.accounting.journalEntries.create({ + serviceId: "quickbooks", + journalEntry: { + title: "Monthly depreciation", + currency: "USD", + line_items: [ + { + type: "debit", + ledger_account: { id: "account_depreciation" }, + total_amount: 500, + description: "Depreciation expense", + }, + { + type: "credit", + ledger_account: { id: "account_accumulated_dep" }, + total_amount: 500, + description: "Accumulated depreciation", + }, + ], + }, +}); +``` + +## Tax Rates (read-only) + +```typescript +const { data } = await apideck.accounting.taxRates.list({ + serviceId: "quickbooks", +}); + +const { data } = await apideck.accounting.taxRates.get({ + id: "tax_rate_1", + serviceId: "quickbooks", +}); +``` + +## Credit Notes + +```typescript +const { data } = await apideck.accounting.creditNotes.create({ + serviceId: "xero", + creditNote: { + number: "CN-001", + customer: { id: "customer_123" }, + currency: "USD", + line_items: [ + { description: "Refund for defective item", quantity: 1, unit_price: 100, total_amount: 100 }, + ], + }, +}); +``` + +## Purchase Orders + +```typescript +const { data } = await apideck.accounting.purchaseOrders.create({ + serviceId: "quickbooks", + purchaseOrder: { + po_number: "PO-001", + supplier: { id: "supplier_123" }, + line_items: [ + { description: "Laptop", quantity: 5, unit_price: 1200, total_amount: 6000 }, + ], + }, +}); +``` + +## Reports + +```typescript +// Balance sheet +const { data } = await apideck.accounting.balanceSheet.get({ + serviceId: "quickbooks", + filter: { start_date: "2024-01-01", end_date: "2024-12-31" }, +}); +// Returns: assets, liabilities, equity with nested account breakdowns + +// Profit & Loss +const { data } = await apideck.accounting.profitAndLoss.get({ + serviceId: "quickbooks", + filter: { start_date: "2024-01-01", end_date: "2024-12-31" }, +}); +// Returns: income, expenses, net_income/net_loss with category breakdowns + +// Aged Debtors +const { data } = await apideck.accounting.agedDebtors.get({ + serviceId: "quickbooks", +}); + +// Aged Creditors +const { data } = await apideck.accounting.agedCreditors.get({ + serviceId: "quickbooks", +}); +``` + +## Expenses + +```typescript +const { data } = await apideck.accounting.expenses.create({ + serviceId: "quickbooks", + expense: { + transaction_date: "2024-06-15", + account: { id: "account_123" }, + currency: "USD", + line_items: [ + { description: "Client dinner", total_amount: 150, account: { id: "account_meals" } }, + ], + }, +}); +``` + +## Bill Payments + +```typescript +const { data } = await apideck.accounting.billPayments.create({ + serviceId: "quickbooks", + billPayment: { + supplier: { id: "supplier_123" }, + total_amount: 250, + currency: "USD", + allocations: [{ id: "bill_123", type: "bill", amount: 250 }], + }, +}); +``` + +## Tracking Categories + +```typescript +const { data } = await apideck.accounting.trackingCategories.list({ + serviceId: "xero", +}); +// Returns categories like departments, projects, cost centers +``` + +## Attachments + +```typescript +// List attachments for a resource +const { data } = await apideck.accounting.attachments.list({ + referenceType: "invoice", + referenceId: "invoice_123", + serviceId: "quickbooks", +}); + +// Download attachment +const { data } = await apideck.accounting.attachments.download({ + referenceType: "invoice", + referenceId: "invoice_123", + id: "attachment_123", + serviceId: "quickbooks", +}); +``` + +## Company Info + +```typescript +const { data } = await apideck.accounting.companyInfo.get({ + serviceId: "quickbooks", +}); +// Returns: legal_name, company_name, currency, fiscal_year_start_month, addresses, phone_numbers +``` diff --git a/providers/claude/plugin/skills/apideck-node/references/ats-api.md b/providers/claude/plugin/skills/apideck-node/references/ats-api.md new file mode 100644 index 0000000..7647207 --- /dev/null +++ b/providers/claude/plugin/skills/apideck-node/references/ats-api.md @@ -0,0 +1,121 @@ +# ATS API Reference + +Base namespace: `apideck.ats` + +Supported connectors: Greenhouse, Lever, Workable, Recruitee, Bullhorn, SAP SuccessFactors, Teamtailor, and more. + +## Jobs + +Jobs are read-only in the unified model. + +```typescript +// List jobs +const { data } = await apideck.ats.jobs.list({ + serviceId: "greenhouse", + limit: 50, +}); + +// Get job details +const { data } = await apideck.ats.jobs.get({ + id: "job_123", + serviceId: "greenhouse", +}); +``` + +Key job fields: `id`, `title`, `description`, `status` (`draft` | `open` | `closed`), `department`, `branch`, `recruiters[]`, `hiring_managers[]`, `addresses[]`, `confidential`, `salary`, `tags[]`, `created_at`, `updated_at`, `closing_date`. + +## Applicants + +```typescript +// List applicants +const { data } = await apideck.ats.applicants.list({ + serviceId: "greenhouse", + limit: 50, +}); + +// Create applicant +const { data } = await apideck.ats.applicants.create({ + serviceId: "greenhouse", + applicant: { + first_name: "Sarah", + last_name: "Chen", + emails: [{ email: "sarah@example.com", type: "primary" }], + phone_numbers: [{ number: "+1234567890", type: "mobile" }], + title: "Senior Software Engineer", + websites: [ + { url: "https://github.com/sarahchen", type: "github" }, + { url: "https://linkedin.com/in/sarahchen", type: "linkedin" }, + ], + social_links: [ + { url: "https://twitter.com/sarahchen", type: "twitter" }, + ], + tags: ["referral", "senior"], + }, +}); + +// Get applicant +const { data } = await apideck.ats.applicants.get({ + id: "applicant_123", + serviceId: "greenhouse", +}); + +// Update applicant +const { data } = await apideck.ats.applicants.update({ + id: "applicant_123", + serviceId: "greenhouse", + applicant: { + tags: ["referral", "senior", "fast-track"], + }, +}); + +// Delete applicant +await apideck.ats.applicants.delete({ + id: "applicant_123", + serviceId: "greenhouse", +}); +``` + +Key applicant fields: `id`, `first_name`, `last_name`, `name`, `title`, `emails[]`, `phone_numbers[]`, `addresses[]`, `websites[]`, `social_links[]`, `stage_id`, `recruiter_id`, `coordinator_id`, `applications[]`, `tags[]`, `sources[]`, `confidential`, `custom_fields[]`. + +## Applications + +```typescript +// List applications +const { data } = await apideck.ats.applications.list({ + serviceId: "greenhouse", +}); + +// Create application (link applicant to job) +const { data } = await apideck.ats.applications.create({ + serviceId: "greenhouse", + application: { + applicant_id: "applicant_123", + job_id: "job_456", + stage_id: "stage_phone_screen", + status: "open", + }, +}); + +// Get application +const { data } = await apideck.ats.applications.get({ + id: "application_123", + serviceId: "greenhouse", +}); + +// Update application (advance stage) +const { data } = await apideck.ats.applications.update({ + id: "application_123", + serviceId: "greenhouse", + application: { + stage_id: "stage_onsite_interview", + }, +}); + +// Delete application +await apideck.ats.applications.delete({ + id: "application_123", + serviceId: "greenhouse", +}); +``` + +Key application fields: `id`, `applicant_id`, `job_id`, `status` (`open` | `rejected` | `hired`), `stage`, `current_stage`, `reject_reason`, `source`, `custom_fields[]`, `created_at`, `updated_at`. diff --git a/providers/claude/plugin/skills/apideck-node/references/crm-api.md b/providers/claude/plugin/skills/apideck-node/references/crm-api.md new file mode 100644 index 0000000..764b3aa --- /dev/null +++ b/providers/claude/plugin/skills/apideck-node/references/crm-api.md @@ -0,0 +1,273 @@ +# CRM API Reference + +Base namespace: `apideck.crm` + +Supported connectors: Salesforce, HubSpot, Pipedrive, Microsoft Dynamics 365, Zoho CRM, Close, Copper, Freshsales, and 15+ more. + +## Contacts + +```typescript +// List contacts with filtering and sorting +const { data } = await apideck.crm.contacts.list({ + serviceId: "salesforce", + limit: 50, + filter: { + name: "John", + email: "john@example.com", + phone_number: "+1234567890", + company_id: "company_123", + owner_id: "user_456", + first_name: "John", + last_name: "Doe", + }, + sort: { by: "updated_at", direction: "desc" }, +}); + +// Create contact +const { data } = await apideck.crm.contacts.create({ + serviceId: "salesforce", + contact: { + first_name: "John", + last_name: "Doe", + title: "VP of Engineering", + department: "Engineering", + company_id: "company_123", + emails: [ + { email: "john@example.com", type: "primary" }, + { email: "john.doe@personal.com", type: "secondary" }, + ], + phone_numbers: [ + { number: "+1234567890", type: "mobile" }, + ], + addresses: [ + { + type: "primary", + street_1: "123 Main St", + city: "San Francisco", + state: "CA", + postal_code: "94105", + country: "US", + }, + ], + social_links: [ + { url: "https://linkedin.com/in/johndoe", type: "linkedin" }, + ], + tags: ["vip", "enterprise"], + custom_fields: [ + { id: "lead_source", value: "Website" }, + ], + }, +}); + +// Get contact +const { data } = await apideck.crm.contacts.get({ + id: "contact_123", + serviceId: "salesforce", +}); + +// Update contact +const { data } = await apideck.crm.contacts.update({ + id: "contact_123", + serviceId: "salesforce", + contact: { title: "CTO" }, +}); + +// Delete contact +await apideck.crm.contacts.delete({ + id: "contact_123", + serviceId: "salesforce", +}); +``` + +Key contact fields: `id`, `name`, `first_name`, `last_name`, `title`, `department`, `company_id`, `company_name`, `emails[]`, `phone_numbers[]`, `addresses[]`, `social_links[]`, `tags[]`, `owner_id`, `lead_source`, `description`, `custom_fields[]`, `created_at`, `updated_at`. + +## Companies + +```typescript +// List companies +const { data } = await apideck.crm.companies.list({ + serviceId: "hubspot", + filter: { name: "Acme" }, + sort: { by: "name", direction: "asc" }, +}); + +// Create company +const { data } = await apideck.crm.companies.create({ + serviceId: "hubspot", + company: { + name: "Acme Corporation", + industry: "Technology", + website: "https://acme.com", + annual_revenue: "10000000", + number_of_employees: "500", + emails: [{ email: "info@acme.com", type: "primary" }], + phone_numbers: [{ number: "+1234567890", type: "primary" }], + addresses: [ + { + type: "primary", + street_1: "456 Tech Blvd", + city: "San Francisco", + state: "CA", + postal_code: "94105", + country: "US", + }, + ], + }, +}); +``` + +Key company fields: `id`, `name`, `industry`, `website`, `annual_revenue`, `number_of_employees`, `owner_id`, `emails[]`, `phone_numbers[]`, `addresses[]`, `social_links[]`, `tags[]`, `description`, `custom_fields[]`. + +## Leads + +```typescript +// List leads +const { data } = await apideck.crm.leads.list({ + serviceId: "salesforce", + filter: { email: "lead@example.com", name: "Jane" }, +}); + +// Create lead +const { data } = await apideck.crm.leads.create({ + serviceId: "salesforce", + lead: { + name: "Jane Smith", + first_name: "Jane", + last_name: "Smith", + company_name: "Startup Inc", + title: "CEO", + emails: [{ email: "jane@startup.com", type: "primary" }], + phone_numbers: [{ number: "+1987654321", type: "mobile" }], + lead_source: "Webinar", + monetary_amount: 50000, + currency: "USD", + }, +}); + +// Convert lead (connector-specific, use pass-through) +const { data } = await apideck.crm.leads.update({ + id: "lead_123", + serviceId: "salesforce", + lead: { + pass_through: [ + { + service_id: "salesforce", + extend_object: { Status: "Converted" }, + }, + ], + }, +}); +``` + +## Opportunities + +```typescript +// List opportunities +const { data } = await apideck.crm.opportunities.list({ + serviceId: "pipedrive", + filter: { status: "open" }, +}); + +// Create opportunity +const { data } = await apideck.crm.opportunities.create({ + serviceId: "pipedrive", + opportunity: { + title: "Enterprise deal - Acme Corp", + primary_contact_id: "contact_123", + company_id: "company_456", + pipeline_id: "pipeline_789", + stage_id: "stage_negotiation", + status: "open", + monetary_amount: 150000, + currency: "USD", + win_probability: 60, + close_date: "2024-09-30", + owner_id: "user_123", + }, +}); + +// Update opportunity stage +const { data } = await apideck.crm.opportunities.update({ + id: "opportunity_123", + serviceId: "pipedrive", + opportunity: { + stage_id: "stage_closed_won", + status: "won", + win_probability: 100, + }, +}); +``` + +Key opportunity fields: `id`, `title`, `primary_contact_id`, `company_id`, `pipeline_id`, `stage_id`, `status` (`open` | `won` | `lost`), `monetary_amount`, `currency`, `win_probability`, `close_date`, `owner_id`, `priority`, `tags[]`. + +## Activities + +```typescript +// List activities +const { data } = await apideck.crm.activities.list({ + serviceId: "salesforce", +}); + +// Create activity (call, meeting, email, task, etc.) +const { data } = await apideck.crm.activities.create({ + serviceId: "salesforce", + activity: { + type: "call", + title: "Follow-up call with Acme", + description: "Discuss contract renewal", + activity_date: "2024-07-01T10:00:00.000Z", + duration_seconds: 1800, + owner_id: "user_123", + contact_id: "contact_456", + company_id: "company_789", + }, +}); +``` + +Activity types: `call`, `meeting`, `email`, `note`, `task`, `deadline`, `send_letter`, `send_quote`, `other`. + +## Notes + +```typescript +// List notes +const { data } = await apideck.crm.notes.list({ + serviceId: "hubspot", +}); + +// Create note +const { data } = await apideck.crm.notes.create({ + serviceId: "hubspot", + note: { + title: "Meeting summary", + content: "Discussed Q3 renewal. Client interested in premium tier.", + contact_id: "contact_123", + company_id: "company_456", + opportunity_id: "opportunity_789", + }, +}); +``` + +## Pipelines + +```typescript +// List pipelines +const { data } = await apideck.crm.pipelines.list({ + serviceId: "pipedrive", +}); +// Returns: id, name, stages[{ id, name, display_order }], currency + +// Get pipeline with stages +const { data } = await apideck.crm.pipelines.get({ + id: "pipeline_123", + serviceId: "pipedrive", +}); +``` + +## Users + +```typescript +const { data } = await apideck.crm.users.list({ + serviceId: "salesforce", +}); +// Returns CRM users: id, name, email, role, status +``` diff --git a/providers/claude/plugin/skills/apideck-node/references/file-storage-api.md b/providers/claude/plugin/skills/apideck-node/references/file-storage-api.md new file mode 100644 index 0000000..5bdd059 --- /dev/null +++ b/providers/claude/plugin/skills/apideck-node/references/file-storage-api.md @@ -0,0 +1,196 @@ +# File Storage API Reference + +Base namespace: `apideck.fileStorage` + +Supported connectors: Box, Dropbox, Google Drive, OneDrive, SharePoint. + +## Files + +```typescript +// List files in a folder +const { data } = await apideck.fileStorage.files.list({ + serviceId: "google-drive", + filter: { + folder_id: "folder_123", + drive_id: "drive_456", + }, + sort: { by: "updated_at", direction: "desc" }, +}); + +// Search files +const { data } = await apideck.fileStorage.files.search({ + serviceId: "google-drive", + filesSearch: { + query: "quarterly report", + }, +}); + +// Get file metadata +const { data } = await apideck.fileStorage.files.get({ + id: "file_123", + serviceId: "google-drive", +}); + +// Upload a file +const { data } = await apideck.fileStorage.files.create({ + serviceId: "google-drive", + createFileRequest: { + name: "report.pdf", + parent_folder_id: "folder_123", + drive_id: "drive_456", + description: "Q3 financial report", + }, +}); + +// Download a file +const { data } = await apideck.fileStorage.files.download({ + id: "file_123", + serviceId: "google-drive", +}); + +// Update file metadata +const { data } = await apideck.fileStorage.files.update({ + id: "file_123", + serviceId: "google-drive", + updateFileRequest: { + name: "report-v2.pdf", + description: "Updated Q3 report", + }, +}); + +// Delete file +await apideck.fileStorage.files.delete({ + id: "file_123", + serviceId: "google-drive", +}); +``` + +Key file fields: `id`, `name`, `description`, `mime_type`, `size`, `parent_folders[]`, `owner`, `permissions[]`, `downloadable`, `exportable`, `created_at`, `updated_at`. + +## Folders + +```typescript +// Get folder contents +const { data } = await apideck.fileStorage.folders.get({ + id: "folder_123", + serviceId: "dropbox", +}); + +// Create folder +const { data } = await apideck.fileStorage.folders.create({ + serviceId: "dropbox", + createFolderRequest: { + name: "Project Documents", + parent_folder_id: "folder_root", + drive_id: "drive_123", + }, +}); + +// Update folder +const { data } = await apideck.fileStorage.folders.update({ + id: "folder_123", + serviceId: "dropbox", + updateFolderRequest: { + name: "Project Docs - Archived", + }, +}); + +// Copy folder +const { data } = await apideck.fileStorage.folders.copy({ + id: "folder_123", + serviceId: "dropbox", + copyFolderRequest: { + parent_folder_id: "folder_destination", + name: "Project Documents (Copy)", + }, +}); + +// Delete folder +await apideck.fileStorage.folders.delete({ + id: "folder_123", + serviceId: "dropbox", +}); +``` + +## Shared Links + +```typescript +// List shared links +const { data } = await apideck.fileStorage.sharedLinks.list({ + serviceId: "box", +}); + +// Create shared link +const { data } = await apideck.fileStorage.sharedLinks.create({ + serviceId: "box", + sharedLink: { + target: { id: "file_123", type: "file" }, + scope: "company", + password: "secure-password", + expires_at: "2024-12-31T23:59:59.000Z", + }, +}); + +// Delete shared link +await apideck.fileStorage.sharedLinks.delete({ + id: "link_123", + serviceId: "box", +}); +``` + +Shared link scopes: `public`, `company`, `password`. + +## Upload Sessions (large files) + +For files larger than the direct upload limit, use chunked upload sessions: + +```typescript +// 1. Create upload session +const { data: session } = await apideck.fileStorage.uploadSessions.create({ + serviceId: "box", + createUploadSessionRequest: { + name: "large-backup.zip", + parent_folder_id: "folder_123", + size: 104857600, // 100MB in bytes + }, +}); + +// 2. Upload parts (SDK handles chunking) +await apideck.fileStorage.uploadSessions.upload({ + id: session.id, + serviceId: "box", + // file part data +}); + +// 3. Finish upload session +const { data: file } = await apideck.fileStorage.uploadSessions.finish({ + id: session.id, + serviceId: "box", +}); +``` + +## Drives + +```typescript +// List drives +const { data } = await apideck.fileStorage.drives.list({ + serviceId: "google-drive", +}); + +// Create drive (shared drives) +const { data } = await apideck.fileStorage.drives.create({ + serviceId: "google-drive", + drive: { + name: "Engineering Shared Drive", + description: "Shared documents for the engineering team", + }, +}); +``` + +## Drive Groups (SharePoint only) + +```typescript +const { data } = await apideck.fileStorage.driveGroups.list({ + serviceId: "sharepoint", +}); +``` diff --git a/providers/claude/plugin/skills/apideck-node/references/hris-api.md b/providers/claude/plugin/skills/apideck-node/references/hris-api.md new file mode 100644 index 0000000..0b5d1e6 --- /dev/null +++ b/providers/claude/plugin/skills/apideck-node/references/hris-api.md @@ -0,0 +1,212 @@ +# HRIS API Reference + +Base namespace: `apideck.hris` + +Supported connectors: Workday, BambooHR, Hibob, SAP SuccessFactors, Personio, Gusto, Rippling, Deel, ADP, Namely, Payfit, and 40+ more. + +## Employees + +```typescript +// List employees +const { data } = await apideck.hris.employees.list({ + serviceId: "bamboohr", + limit: 50, + filter: { + company_id: "company_123", + department_id: "dept_456", + employment_status: "active", + manager_id: "emp_789", + title: "Engineer", + }, + sort: { by: "last_name", direction: "asc" }, +}); + +// Create employee +const { data } = await apideck.hris.employees.create({ + serviceId: "bamboohr", + employee: { + first_name: "Alice", + last_name: "Johnson", + display_name: "Alice Johnson", + gender: "female", + birthday: "1990-05-15", + emails: [{ email: "alice@company.com", type: "work" }], + phone_numbers: [{ number: "+1234567890", type: "mobile" }], + addresses: [ + { + type: "primary", + street_1: "789 Oak Ave", + city: "Austin", + state: "TX", + postal_code: "73301", + country: "US", + }, + ], + employment_status: "active", + employment_role: { type: "employee", sub_type: "full_time" }, + department_id: "dept_engineering", + title: "Senior Software Engineer", + manager: { id: "emp_manager_123" }, + start_date: "2024-03-01", + compensations: [ + { + rate: 150000, + payment_unit: "year", + currency: "USD", + effective_date: "2024-03-01", + }, + ], + }, +}); + +// Get employee +const { data } = await apideck.hris.employees.get({ + id: "emp_123", + serviceId: "bamboohr", +}); + +// Update employee +const { data } = await apideck.hris.employees.update({ + id: "emp_123", + serviceId: "bamboohr", + employee: { + title: "Staff Software Engineer", + compensations: [ + { + rate: 175000, + payment_unit: "year", + currency: "USD", + effective_date: "2025-01-01", + }, + ], + }, +}); + +// Delete employee +await apideck.hris.employees.delete({ + id: "emp_123", + serviceId: "bamboohr", +}); +``` + +Key employee fields: `id`, `first_name`, `last_name`, `display_name`, `title`, `department_id`, `department`, `company_id`, `company_name`, `employment_status` (`active` | `inactive` | `terminated`), `employment_role`, `manager`, `start_date`, `termination_date`, `birthday`, `gender`, `emails[]`, `phone_numbers[]`, `addresses[]`, `jobs[]`, `compensations[]`, `teams[]`, `social_links[]`, `custom_fields[]`. + +Employment role types: `employee`, `contractor`. Sub-types: `full_time`, `part_time`, `intern`, `freelance`, `temp`, `seasonal`. + +## Departments + +```typescript +// List departments +const { data } = await apideck.hris.departments.list({ + serviceId: "bamboohr", +}); + +// Create department +const { data } = await apideck.hris.departments.create({ + serviceId: "bamboohr", + department: { + name: "Product Engineering", + code: "PROD-ENG", + parent_id: "dept_engineering", + }, +}); +``` + +## Companies + +```typescript +// List HRIS companies (for multi-entity organizations) +const { data } = await apideck.hris.companies.list({ + serviceId: "bamboohr", +}); + +// Get company details +const { data } = await apideck.hris.companies.get({ + id: "company_123", + serviceId: "bamboohr", +}); +// Returns: legal_name, display_name, status, addresses, phone_numbers, ein (tax ID) +``` + +## Payrolls + +```typescript +// List payrolls +const { data } = await apideck.hris.payrolls.list({ + serviceId: "gusto", + filter: { + start_date: "2024-01-01", + end_date: "2024-01-31", + }, +}); + +// Get specific payroll +const { data } = await apideck.hris.payrolls.get({ + payrollId: "payroll_123", + serviceId: "gusto", +}); +// Returns: id, processed_date, check_date, totals (gross_pay, net_pay, total_tax, total_deductions) +``` + +## Employee Payrolls + +```typescript +// List employee payrolls for a specific employee +const { data } = await apideck.hris.employeePayrolls.list({ + employeeId: "emp_123", + serviceId: "gusto", +}); +// Returns per-employee: gross_pay, net_pay, taxes[], deductions[], compensations[] +``` + +## Time-Off Requests + +```typescript +// List time-off requests +const { data } = await apideck.hris.timeOffRequests.list({ + serviceId: "bamboohr", + filter: { + employee_id: "emp_123", + start_date: "2024-06-01", + end_date: "2024-06-30", + }, +}); + +// Create time-off request +const { data } = await apideck.hris.timeOffRequests.create({ + serviceId: "bamboohr", + timeOffRequest: { + employee_id: "emp_123", + policy_id: "policy_pto", + start_date: "2024-07-15", + end_date: "2024-07-19", + status: "requested", + request_type: "vacation", + notes: { employee: "Family vacation" }, + }, +}); + +// Update time-off request (approve/deny) +const { data } = await apideck.hris.timeOffRequests.update({ + id: "time_off_123", + serviceId: "bamboohr", + timeOffRequest: { + status: "approved", + notes: { manager: "Approved. Enjoy!" }, + }, +}); +``` + +Time-off statuses: `requested`, `approved`, `declined`, `cancelled`, `deleted`. +Request types: `vacation`, `sick`, `personal`, `jury_duty`, `volunteer`, `bereavement`. + +## Employee Schedules + +```typescript +// List employee schedules +const { data } = await apideck.hris.employeeSchedules.list({ + employeeId: "emp_123", + serviceId: "bamboohr", +}); +// Returns weekly schedule patterns with work days and hours +``` diff --git a/providers/claude/plugin/skills/apideck-node/references/vault-api.md b/providers/claude/plugin/skills/apideck-node/references/vault-api.md new file mode 100644 index 0000000..2969ac9 --- /dev/null +++ b/providers/claude/plugin/skills/apideck-node/references/vault-api.md @@ -0,0 +1,164 @@ +# Vault API Reference + +Base namespace: `apideck.vault` + +Vault manages authentication and connections between your application and downstream services. It provides embeddable UI components for end-users to authorize their own integrations. + +## Connections + +```typescript +// List all connections for a consumer +const { data } = await apideck.vault.connections.list({ + api: "crm", +}); +// Returns: id, service_id, name, state, auth_type, enabled, created_at + +// Get connection details +const { data } = await apideck.vault.connections.get({ + serviceId: "salesforce", + unifiedApi: "crm", +}); + +// Update connection settings +const { data } = await apideck.vault.connections.update({ + serviceId: "salesforce", + unifiedApi: "crm", + connection: { + enabled: true, + settings: { + instance_url: "https://mycompany.salesforce.com", + }, + }, +}); + +// Delete connection +await apideck.vault.connections.delete({ + serviceId: "salesforce", + unifiedApi: "crm", +}); + +// Import connection (bring existing OAuth tokens) +const { data } = await apideck.vault.connections.imports({ + serviceId: "salesforce", + unifiedApi: "crm", + connectionImportData: { + credentials: { + access_token: "existing-access-token", + refresh_token: "existing-refresh-token", + }, + }, +}); +``` + +Connection states: `available`, `callable`, `added`, `authorized`, `invalid`. + +## Connection Settings + +```typescript +// Get available settings for a connection +const { data } = await apideck.vault.connectionSettings.list({ + unifiedApi: "accounting", + serviceId: "quickbooks", + resource: "invoices", +}); +// Returns configurable fields, allowed values, and current settings +``` + +## Sessions + +Create a Vault session URL to embed the connection management UI: + +```typescript +// Create session for Vault embedded UI +const { data } = await apideck.vault.sessions.create({ + session: { + consumer_metadata: { + account_name: "Acme Corp", + user_name: "John Doe", + email: "john@acme.com", + image: "https://acme.com/john.jpg", + }, + redirect_uri: "https://myapp.com/integrations", + settings: { + unified_apis: ["accounting", "crm"], + hide_resource_settings: false, + sandbox_mode: false, + auto_redirect: false, + }, + theme: { + vault_name: "My App Integrations", + primary_color: "#4F46E5", + sidepanel_background_color: "#F9FAFB", + sidepanel_text_color: "#111827", + favicon: "https://myapp.com/favicon.ico", + logo: "https://myapp.com/logo.png", + }, + }, +}); +// data.session_uri -> redirect user here to manage connections +``` + +## Consumers + +```typescript +// List consumers +const { data } = await apideck.vault.consumers.list({ limit: 50 }); + +// Create consumer +const { data } = await apideck.vault.consumers.create({ + consumer: { + consumer_id: "user_abc123", + metadata: { + account_name: "Acme Corp", + user_name: "John Doe", + email: "john@acme.com", + }, + }, +}); + +// Get consumer +const { data } = await apideck.vault.consumers.get({ + consumerId: "user_abc123", +}); + +// Delete consumer and all their connections +await apideck.vault.consumers.delete({ + consumerId: "user_abc123", +}); +``` + +## Custom Mappings + +Map connector-specific fields to your unified model: + +```typescript +// List custom mappings for a connection +const { data } = await apideck.vault.customMappings.list({ + unifiedApi: "crm", + serviceId: "salesforce", +}); + +// Update custom field mapping +const { data } = await apideck.vault.customMappings.update({ + unifiedApi: "crm", + serviceId: "salesforce", + id: "mapping_123", + customMapping: { + value: "$.custom_sf_field__c", + }, +}); +``` + +## Logs + +```typescript +// List API call logs +const { data } = await apideck.vault.logs.list({ + filter: { + connector_id: "salesforce", + status_code: 200, + exclude_unified_apis: "vault,proxy", + }, +}); +// Returns: api_style, base_url, path, method, status_code, duration, consumer_id, service_id, timestamp +``` diff --git a/providers/claude/plugin/skills/apideck-node/references/vault-js.md b/providers/claude/plugin/skills/apideck-node/references/vault-js.md new file mode 100644 index 0000000..eb7e455 --- /dev/null +++ b/providers/claude/plugin/skills/apideck-node/references/vault-js.md @@ -0,0 +1,293 @@ +# Vault JS Integration Reference + +Vault JS is a vanilla JavaScript library to embed Apideck Vault in any web application. It lets your users authorize connectors and manage integration settings through a pre-built modal UI, and stores credentials securely so you can make authorized API calls on their behalf. + +## Installation + +```bash +npm install @apideck/vault-js +``` + +Or load via CDN (available globally as `window.ApideckVault`): + +```html + +``` + +## Prerequisites + +Session creation MUST happen server-side to prevent token leakage. Create a session using the `@apideck/unify` SDK before opening Vault: + +```typescript +import { Apideck } from "@apideck/unify"; + +const apideck = new Apideck({ + apiKey: process.env["APIDECK_API_KEY"] ?? "", + appId: "your-app-id", + consumerId: "user_abc123", +}); + +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"], + hide_resource_settings: false, + sandbox_mode: false, + }, + theme: { + vault_name: "My App Integrations", + primary_color: "#4F46E5", + sidepanel_background_color: "#F9FAFB", + sidepanel_text_color: "#111827", + favicon: "https://myapp.com/favicon.ico", + logo: "https://myapp.com/logo.png", + }, + }, +}); + +const sessionToken = data.session_token; +// Pass this token to your frontend +``` + +## Opening Vault + +```typescript +import { ApideckVault } from "@apideck/vault-js"; + +// Basic usage — open Vault modal with a session token +ApideckVault.open({ + token: "SESSION_TOKEN_FROM_BACKEND", +}); +``` + +## Configuration Options + +```typescript +ApideckVault.open({ + // REQUIRED: JWT session token from your backend + token: "SESSION_TOKEN_FROM_BACKEND", + + // Show integrations for a single Unified API only + unifiedApi: "accounting", + + // Open Vault for a single integration only + serviceId: "quickbooks", + + // Initial view to display + // "settings" | "configurable-resources" | "custom-mapping" + initialView: "settings", + + // Locale for the modal UI + // "en" (default) | "nl" | "de" | "fr" | "es" + locale: "en", + + // Show language switch in the modal + showLanguageSwitch: true, + + // Use button layout instead of dropdown menus + showButtonLayout: false, + + // Automatically start OAuth flow when opening a connection + autoStartAuthorization: true, + + // Callback when modal is ready and visible + onReady: () => { + console.log("Vault modal is open"); + }, + + // Callback when modal is closed + onClose: () => { + console.log("Vault modal closed"); + }, + + // Callback when a connection is added, updated, or authorized + onConnectionChange: (connection) => { + console.log("Connection changed:", connection); + // connection: { id, service_id, unified_api, state, ... } + }, + + // Callback when a connection is deleted + onConnectionDelete: (connection) => { + console.log("Connection deleted:", connection); + }, +}); +``` + +## Closing Vault Programmatically + +```typescript +ApideckVault.close(); +``` + +## Full Integration Example + +### Server-side (Node.js / Express) + +```typescript +import express from "express"; +import { Apideck } from "@apideck/unify"; + +const app = express(); + +const apideck = new Apideck({ + apiKey: process.env["APIDECK_API_KEY"] ?? "", + appId: process.env["APIDECK_APP_ID"] ?? "", + consumerId: "", // set per-request +}); + +// Create a Vault session for the authenticated user +app.post("/api/vault/session", async (req, res) => { + const userId = req.user.id; // from your auth middleware + + const client = new Apideck({ + apiKey: process.env["APIDECK_API_KEY"] ?? "", + appId: process.env["APIDECK_APP_ID"] ?? "", + consumerId: userId, + }); + + const { data } = await client.vault.sessions.create({ + session: { + consumer_metadata: { + account_name: req.user.company_name, + user_name: req.user.name, + email: req.user.email, + }, + redirect_uri: `${process.env["APP_URL"]}/integrations`, + settings: { + unified_apis: ["accounting", "crm", "hris"], + }, + theme: { + vault_name: "My App", + primary_color: "#4F46E5", + }, + }, + }); + + res.json({ token: data.session_token }); +}); +``` + +### Client-side (Vanilla JS) + +```typescript +import { ApideckVault } from "@apideck/vault-js"; + +async function openVault() { + // Fetch session token from your backend + const response = await fetch("/api/vault/session", { method: "POST" }); + const { token } = await response.json(); + + ApideckVault.open({ + token, + onReady: () => console.log("Vault opened"), + onClose: () => console.log("Vault closed"), + onConnectionChange: (connection) => { + console.log(`${connection.service_id} is now ${connection.state}`); + // Refresh your integrations list + loadIntegrations(); + }, + onConnectionDelete: (connection) => { + console.log(`${connection.service_id} disconnected`); + loadIntegrations(); + }, + }); +} + +document.getElementById("manage-integrations") + ?.addEventListener("click", openVault); +``` + +### Client-side (React) + +```tsx +import { ApideckVault } from "@apideck/vault-js"; +import { useCallback, useState } from "react"; + +function IntegrationsButton() { + const [isLoading, setIsLoading] = useState(false); + + const handleOpenVault = useCallback(async () => { + setIsLoading(true); + try { + const response = await fetch("/api/vault/session", { method: "POST" }); + const { token } = await response.json(); + + ApideckVault.open({ + token, + onReady: () => setIsLoading(false), + onClose: () => console.log("Vault closed"), + onConnectionChange: (connection) => { + console.log("Connection changed:", connection); + }, + }); + } catch (error) { + console.error("Failed to create Vault session:", error); + setIsLoading(false); + } + }, []); + + return ( + + ); +} +``` + +## Filtering by API or Connector + +```typescript +// Show only accounting integrations +ApideckVault.open({ + token: "SESSION_TOKEN", + unifiedApi: "accounting", +}); + +// Open directly to QuickBooks settings +ApideckVault.open({ + token: "SESSION_TOKEN", + serviceId: "quickbooks", + initialView: "settings", +}); + +// Open custom field mapping for Salesforce +ApideckVault.open({ + token: "SESSION_TOKEN", + serviceId: "salesforce", + initialView: "custom-mapping", +}); +``` + +## Theming + +Theme customization is set during session creation on the server side: + +```typescript +const { data } = await apideck.vault.sessions.create({ + session: { + theme: { + vault_name: "My App Integrations", + primary_color: "#4F46E5", + sidepanel_background_color: "#F9FAFB", + sidepanel_text_color: "#111827", + favicon: "https://myapp.com/favicon.ico", + logo: "https://myapp.com/logo.png", + }, + }, +}); +``` + +| Theme Property | Description | +|----------------|-------------| +| `vault_name` | Display name shown in the modal header | +| `primary_color` | Primary accent color (hex) | +| `sidepanel_background_color` | Side panel background (hex) | +| `sidepanel_text_color` | Side panel text color (hex) | +| `favicon` | URL to your favicon | +| `logo` | URL to your logo image | diff --git a/providers/claude/plugin/skills/apideck-node/references/webhook-api.md b/providers/claude/plugin/skills/apideck-node/references/webhook-api.md new file mode 100644 index 0000000..457e579 --- /dev/null +++ b/providers/claude/plugin/skills/apideck-node/references/webhook-api.md @@ -0,0 +1,162 @@ +# Webhook API Reference + +Base namespace: `apideck.webhook` + +Apideck supports both **native webhooks** (from connectors that support them natively) and **virtual webhooks** (Apideck polls the connector and delivers events to your webhook URL). + +## Webhook Subscriptions + +```typescript +// List webhooks +const { data } = await apideck.webhook.webhooks.list(); + +// Create webhook subscription +const { data } = await apideck.webhook.webhooks.create({ + webhook: { + description: "CRM contact changes", + unified_api: "crm", + status: "enabled", + delivery_url: "https://myapp.com/webhooks/apideck", + events: [ + "crm.contact.created", + "crm.contact.updated", + "crm.contact.deleted", + ], + }, +}); + +// Get webhook +const { data } = await apideck.webhook.webhooks.get({ + id: "webhook_123", +}); + +// Update webhook +const { data } = await apideck.webhook.webhooks.update({ + id: "webhook_123", + webhook: { + description: "All CRM changes", + events: [ + "crm.contact.created", + "crm.contact.updated", + "crm.contact.deleted", + "crm.company.created", + "crm.company.updated", + "crm.lead.created", + ], + }, +}); + +// Delete webhook +await apideck.webhook.webhooks.delete({ + id: "webhook_123", +}); +``` + +## Webhook Event Types + +Events follow the pattern `{unified_api}.{resource}.{action}`: + +### Accounting +- `accounting.invoice.created` / `.updated` / `.deleted` +- `accounting.bill.created` / `.updated` / `.deleted` +- `accounting.payment.created` / `.updated` / `.deleted` +- `accounting.customer.created` / `.updated` / `.deleted` +- `accounting.supplier.created` / `.updated` / `.deleted` +- `accounting.ledger-account.created` / `.updated` / `.deleted` +- `accounting.journal-entry.created` / `.updated` / `.deleted` +- `accounting.credit-note.created` / `.updated` / `.deleted` +- `accounting.expense.created` / `.updated` / `.deleted` + +### CRM +- `crm.contact.created` / `.updated` / `.deleted` +- `crm.company.created` / `.updated` / `.deleted` +- `crm.lead.created` / `.updated` / `.deleted` +- `crm.opportunity.created` / `.updated` / `.deleted` +- `crm.activity.created` / `.updated` / `.deleted` +- `crm.note.created` / `.updated` / `.deleted` + +### HRIS +- `hris.employee.created` / `.updated` / `.deleted` / `.terminated` +- `hris.department.created` / `.updated` / `.deleted` +- `hris.company.created` / `.updated` / `.deleted` + +### File Storage +- `file-storage.file.created` / `.updated` / `.deleted` +- `file-storage.folder.created` / `.updated` / `.deleted` + +### ATS +- `ats.applicant.created` / `.updated` / `.deleted` +- `ats.application.created` / `.updated` / `.deleted` +- `ats.job.created` / `.updated` / `.deleted` + +## Webhook Payload Format + +```json +{ + "payload": { + "event_type": "crm.contact.updated", + "unified_api": "crm", + "service_id": "salesforce", + "consumer_id": "user_abc123", + "entity_id": "contact_123", + "entity_type": "contact", + "entity_url": "https://unify.apideck.com/crm/contacts/contact_123", + "occurred_at": "2024-06-15T10:30:00.000Z" + } +} +``` + +## Webhook Signature Verification + +Verify webhook signatures to ensure requests are from Apideck: + +```typescript +import crypto from "crypto"; + +function verifyWebhookSignature( + payload: string, + signature: string, + secret: string +): boolean { + const hmac = crypto.createHmac("sha256", secret); + hmac.update(payload); + const expectedSignature = hmac.digest("base64"); + return crypto.timingSafeEqual( + Buffer.from(signature), + Buffer.from(expectedSignature) + ); +} + +// In your webhook handler +app.post("/webhooks/apideck", (req, res) => { + const signature = req.headers["x-apideck-signature"] as string; + const isValid = verifyWebhookSignature( + JSON.stringify(req.body), + signature, + process.env.APIDECK_WEBHOOK_SECRET! + ); + if (!isValid) { + return res.status(401).send("Invalid signature"); + } + // Process event + const { event_type, entity_id, service_id } = req.body.payload; + console.log(`Received ${event_type} for ${entity_id} from ${service_id}`); + res.status(200).send("OK"); +}); +``` + +## Event Logs + +```typescript +// List webhook event logs +const { data } = await apideck.webhook.eventLogs.list({ + filter: { + exclude_apis: "vault,proxy", + service: { id: "salesforce" }, + consumer_id: "user_abc123", + entity_type: "contact", + event_type: "crm.contact.updated", + }, +}); +// Returns: event delivery status, attempts, request/response details +``` diff --git a/providers/claude/plugin/skills/apideck-php/SKILL.md b/providers/claude/plugin/skills/apideck-php/SKILL.md new file mode 100644 index 0000000..d332489 --- /dev/null +++ b/providers/claude/plugin/skills/apideck-php/SKILL.md @@ -0,0 +1,213 @@ +--- +name: apideck-php +description: Apideck Unified API integration patterns for PHP. 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 using PHP. Covers the apideck-libraries/sdk-php Composer package, authentication, CRUD operations, pagination, error handling, and Vault connection management. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +# Apideck PHP SDK Skill + +## Overview + +The [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 PHP SDK provides typed clients for all unified APIs. + +## Installation + +```sh +composer require apideck-libraries/sdk-php +``` + +## IMPORTANT RULES + +- ALWAYS use the `apideck-libraries/sdk-php` Composer package. DO NOT make raw `Guzzle`/`curl` calls to the Apideck API. +- ALWAYS pass security, app ID, and consumer ID via the builder when creating the client. +- USE `serviceId` on requests to specify which downstream connector to use. +- ALWAYS handle errors with try/catch using `Errors\APIException` as the base class. +- DO NOT store API keys in source code. Use environment variables. + +## Quick Start + +```php +setConsumerId('your-consumer-id') + ->setAppId('your-app-id') + ->setSecurity(getenv('APIDECK_API_KEY')) + ->build(); + +$request = new Operations\CrmContactsAllRequest( + serviceId: 'salesforce', + limit: 20, +); + +$responses = $sdk->crm->contacts->list(request: $request); + +foreach ($responses as $response) { + if ($response->httpMeta->response->getStatusCode() === 200) { + foreach ($response->getContactsResponse->data as $contact) { + echo $contact->name . "\n"; + } + } +} +``` + +## SDK Patterns + +### Client Setup + +```php +use Apideck\Unify; + +$sdk = Unify\Apideck::builder() + ->setConsumerId('your-consumer-id') + ->setAppId('your-app-id') + ->setSecurity(getenv('APIDECK_API_KEY')) + ->build(); +``` + +### CRUD Operations + +All resources follow the pattern: `$sdk->{api}->{resource}->{operation}(request: $request)`. + +```php +use Apideck\Unify\Models\Operations; +use Apideck\Unify\Models\Components; + +// LIST +$request = new Operations\CrmContactsAllRequest( + serviceId: 'salesforce', + limit: 20, + filter: new Components\ContactsFilter(email: 'john@example.com'), + sort: new Components\ContactsSort( + by: Components\ContactsSortBy::UpdatedAt, + direction: Components\SortDirection::Desc, + ), +); +$responses = $sdk->crm->contacts->list(request: $request); + +// CREATE +$request = new Operations\CrmContactsAddRequest( + serviceId: 'salesforce', + contact: new Components\ContactInput( + firstName: 'John', + lastName: 'Doe', + emails: [ + new Components\Email(email: 'john@example.com', type: Components\EmailType::Primary), + ], + phoneNumbers: [ + new Components\PhoneNumber(number: '+1234567890', type: Components\PhoneNumberType::Mobile), + ], + ), +); +$response = $sdk->crm->contacts->create(request: $request); + +// GET +$request = new Operations\CrmContactsOneRequest( + id: 'contact_123', + serviceId: 'salesforce', +); +$response = $sdk->crm->contacts->get(request: $request); + +// UPDATE +$request = new Operations\CrmContactsUpdateRequest( + id: 'contact_123', + serviceId: 'salesforce', + contact: new Components\ContactInput(firstName: 'Jane'), +); +$response = $sdk->crm->contacts->update(request: $request); + +// DELETE +$request = new Operations\CrmContactsDeleteRequest( + id: 'contact_123', + serviceId: 'salesforce', +); +$response = $sdk->crm->contacts->delete(request: $request); +``` + +### Pagination + +The `list` method returns a PHP Generator. Iterate with `foreach`: + +```php +$request = new Operations\AccountingInvoicesAllRequest( + serviceId: 'quickbooks', + limit: 50, +); + +$responses = $sdk->accounting->invoices->list(request: $request); + +foreach ($responses as $response) { + if ($response->httpMeta->response->getStatusCode() === 200) { + foreach ($response->getInvoicesResponse->data as $invoice) { + echo "{$invoice->number}: {$invoice->total}\n"; + } + } +} +``` + +### Error Handling + +```php +use Apideck\Unify\Models\Errors; + +try { + $responses = $sdk->crm->contacts->list(request: $request); + foreach ($responses as $response) { + // handle response + } +} catch (Errors\BadRequestResponseThrowable $e) { + echo "Bad request: " . $e->getMessage() . "\n"; +} catch (Errors\UnauthorizedResponseThrowable $e) { + echo "Unauthorized\n"; +} catch (Errors\NotFoundResponseThrowable $e) { + echo "Not found\n"; +} catch (Errors\PaymentRequiredResponseThrowable $e) { + echo "API limit reached\n"; +} catch (Errors\UnprocessableResponseThrowable $e) { + echo "Validation error: " . $e->getMessage() . "\n"; +} catch (Errors\APIException $e) { + echo "API error: " . $e->getMessage() . "\n"; +} +``` + +### Retry Configuration + +```php +use Apideck\Unify\Utils\Retry; + +// Global +$sdk = Unify\Apideck::builder() + ->setRetryConfig( + new Retry\RetryConfigBackoff( + initialInterval: 1, + maxInterval: 50, + exponent: 1.1, + maxElapsedTime: 100, + retryConnectionErrors: false, + ) + ) + ->setConsumerId('your-consumer-id') + ->setAppId('your-app-id') + ->setSecurity(getenv('APIDECK_API_KEY')) + ->build(); +``` + +## API Namespaces + +| Namespace | Resources | +|-----------|-----------| +| `$sdk->accounting->*` | invoices, bills, payments, customers, suppliers, ledgerAccounts, journalEntries, taxRates, creditNotes, purchaseOrders, balanceSheet, profitAndLoss, and more | +| `$sdk->crm->*` | contacts, companies, leads, opportunities, activities, notes, pipelines, users | +| `$sdk->hris->*` | employees, companies, departments, payrolls, timeOffRequests | +| `$sdk->fileStorage->*` | files, folders, drives, driveGroups, sharedLinks, uploadSessions | +| `$sdk->ats->*` | applicants, applications, jobs | +| `$sdk->vault->*` | connections, consumers, sessions, customMappings, logs | +| `$sdk->webhook->*` | webhooks, eventLogs | diff --git a/providers/claude/plugin/skills/apideck-portman/SKILL.md b/providers/claude/plugin/skills/apideck-portman/SKILL.md new file mode 100644 index 0000000..a549727 --- /dev/null +++ b/providers/claude/plugin/skills/apideck-portman/SKILL.md @@ -0,0 +1,384 @@ +--- +name: apideck-portman +description: API contract testing with Portman by Apideck. Use when generating Postman collections from OpenAPI specs, writing contract tests, variation tests, integration tests, fuzz testing, or setting up CI/CD API test pipelines. Portman converts OpenAPI 3.x specs into Postman collections with auto-generated test suites. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +# Portman API Testing Skill + +## Overview + +[Portman](https://github.com/apideck-libraries/portman) converts OpenAPI 3.x specifications into Postman collections with auto-generated contract tests, variation tests, content tests, and integration tests. It runs tests via Newman (Postman's CLI runner) and integrates into CI/CD pipelines. + +## Installation + +```sh +npm install -g @apideck/portman +``` + +Or use without installing: + +```sh +npx @apideck/portman -l your-openapi-spec.yaml +``` + +## IMPORTANT RULES + +- ALWAYS use a `portman-config.json` (or `.yaml`) for test configuration. Do not rely solely on defaults for production use. +- ALWAYS target operations using `openApiOperationId` or `openApiOperation` (method::path) syntax. +- USE `--baseUrl` to override the spec's server URL when testing against local/staging environments. +- USE `--envFile` to inject environment variables. Variables prefixed with `PORTMAN_` auto-map to Postman collection variables. +- USE `assignVariables` to chain request/response values across operations (e.g., capture `id` from create, use in get/update/delete). +- DO NOT hardcode secrets in portman-config. Use environment variables and `.env` files. + +## Quick Start + +```sh +# Generate collection from local spec +portman -l ./openapi.yaml + +# Generate and run tests against live API +portman -l ./openapi.yaml -b https://api.example.com -n true + +# With custom config +portman -l ./openapi.yaml -c ./portman-config.json -b https://api.example.com -n true +``` + +## Configuration File + +Create `portman-config.json` (or `.yaml`): + +```json +{ + "version": 1.0, + "tests": { + "contractTests": [], + "contentTests": [], + "variationTests": [], + "integrationTests": [], + "extendTests": [] + }, + "assignVariables": [], + "overwrites": [], + "globals": {} +} +``` + +JSON Schema: `https://raw.githubusercontent.com/apideck-libraries/portman/main/src/utils/portman-config-schema.json` + +## Targeting Operations + +All test and overwrite sections use the same targeting system: + +```json +// By operationId +{ "openApiOperationId": "leadsAdd" } + +// By multiple operationIds +{ "openApiOperationIds": ["leadsAdd", "leadsAll"] } + +// By method::path (supports wildcards) +{ "openApiOperation": "GET::/crm/leads" } +{ "openApiOperation": "*::/crm/*" } +{ "openApiOperation": "POST::/*" } + +// Exclude specific operations +{ "openApiOperation": "*::/crm/*", "excludeForOperations": ["leadsDelete"] } +``` + +## Contract Tests + +Validate API responses conform to the OpenAPI spec: + +```json +{ + "tests": { + "contractTests": [ + { + "openApiOperation": "*::/*", + "statusSuccess": { "enabled": true }, + "contentType": { "enabled": true }, + "jsonBody": { "enabled": true }, + "schemaValidation": { "enabled": true }, + "headersPresent": { "enabled": true } + }, + { + "openApiOperation": "*::/*", + "responseTime": { "enabled": true, "maxMs": 300 } + } + ] + } +} +``` + +| Test | Description | +|------|-------------| +| `statusSuccess` | Response returns 2xx | +| `statusCode` | Response returns specific HTTP code | +| `contentType` | Content-Type matches spec | +| `jsonBody` | Body is valid JSON matching spec | +| `schemaValidation` | Body validates against JSON schema | +| `headersPresent` | Required headers are present | +| `responseTime` | Response within `maxMs` milliseconds | + +## Content Tests + +Validate specific response values: + +```json +{ + "tests": { + "contentTests": [ + { + "openApiOperationId": "leadsAll", + "responseBodyTests": [ + { "key": "status_code", "value": 200 }, + { "key": "data[0].id", "assert": "not.to.be.null" }, + { "key": "data", "minLength": 1 }, + { "key": "resource", "oneOf": ["leads", "contacts"] } + ], + "responseHeaderTests": [ + { "key": "content-type", "contains": "application/json" } + ] + } + ] + } +} +``` + +Content test assertions: `value` (exact), `contains` (substring), `oneOf`, `length`, `minLength`, `maxLength`, `notExist`, `assert` (Postman assertion string). + +## Variation Tests + +Test alternative scenarios (errors, edge cases, unauthorized access): + +```json +{ + "tests": { + "variationTests": [ + { + "openApiOperation": "*::/crm/*", + "openApiResponse": "401", + "variations": [ + { + "name": "Unauthorized", + "overwrites": [ + { "overwriteRequestSecurity": { "bearer": { "token": "invalid" } } } + ], + "tests": { + "contractTests": [{ "statusCode": { "enabled": true } }] + } + } + ] + }, + { + "openApiOperationId": "leadsAdd", + "openApiResponse": "400", + "variations": [ + { + "name": "MissingRequiredFields", + "overwrites": [ + { "overwriteRequestBody": [{ "key": "name", "value": "", "overwrite": true }] } + ], + "tests": { + "contractTests": [ + { "statusCode": { "enabled": true } }, + { "schemaValidation": { "enabled": true } } + ] + } + } + ] + } + ] + } +} +``` + +## Fuzz Testing + +Auto-generate invalid values based on schema constraints: + +```json +{ + "tests": { + "variationTests": [ + { + "openApiOperation": "*::/crm/*", + "openApiResponse": "422", + "variations": [ + { + "name": "FuzzTest", + "fuzzing": [ + { + "requestBody": [ + { + "requiredFields": { "enabled": true }, + "minimumNumberFields": { "enabled": true }, + "maximumNumberFields": { "enabled": true }, + "minLengthFields": { "enabled": true }, + "maxLengthFields": { "enabled": true } + } + ] + } + ], + "tests": { + "contractTests": [{ "statusCode": { "enabled": true } }] + } + } + ] + } + ] + } +} +``` + +Fuzzing targets: `requestBody`, `requestQueryParams`, `requestHeaders`. + +## Integration Tests + +Group operations into end-to-end workflows: + +```json +{ + "tests": { + "integrationTests": [ + { + "name": "Lead Lifecycle", + "operations": [ + { "openApiOperationId": "leadsAdd" }, + { "openApiOperationId": "leadsOne" }, + { "openApiOperationId": "leadsUpdate" }, + { "openApiOperationId": "leadsDelete" } + ] + } + ] + } +} +``` + +## Variable Chaining + +Capture values from responses to use in subsequent requests: + +```json +{ + "assignVariables": [ + { + "openApiOperationId": "leadsAdd", + "collectionVariables": [ + { "responseBodyProp": "data.id", "name": "leadId" }, + { "responseHeaderProp": "x-request-id", "name": "requestId" } + ] + } + ] +} +``` + +Use captured variables in overwrites: `{{leadId}}`, `{{requestId}}`. + +## Request Overwrites + +Modify generated requests: + +```json +{ + "overwrites": [ + { + "openApiOperationId": "leadsAdd", + "overwriteRequestBody": [ + { "key": "name", "value": "Test Lead {{$randomInt}}", "overwrite": true } + ], + "overwriteRequestHeaders": [ + { "key": "x-apideck-consumer-id", "value": "{{consumerId}}", "overwrite": true } + ] + }, + { + "openApiOperation": "DELETE::/crm/leads/{id}", + "overwriteRequestPathVariables": [ + { "key": "id", "value": "{{leadId}}", "overwrite": true } + ] + } + ] +} +``` + +Security overwrites: `overwriteRequestSecurity` supports `bearer`, `apiKey`, `basic`, `oauth2`, and `remove`. + +## Globals + +```json +{ + "globals": { + "collectionPreRequestScripts": ["pm.collectionVariables.set('timestamp', Date.now());"], + "securityOverwrites": { + "bearer": { "token": "{{bearerToken}}" } + }, + "keyValueReplacements": { "x-apideck-app-id": "{{applicationId}}" }, + "valueReplacements": { "": "{{bearerToken}}" }, + "orderOfOperations": ["leadsAdd", "leadsAll", "leadsOne", "leadsUpdate", "leadsDelete"], + "stripResponseExamples": true, + "variableCasing": "camelCase" + } +} +``` + +## Environment Variables + +Variables prefixed with `PORTMAN_` in `.env` are auto-injected as camelCase Postman variables: + +``` +PORTMAN_CONSUMER_ID=test_user → {{consumerId}} +PORTMAN_API_TOKEN=abc123 → {{apiToken}} +``` + +## CI/CD Integration + +Store all options in a CLI options file: + +```json +{ + "local": "./specs/crm.yml", + "baseUrl": "https://staging-api.example.com", + "output": "./output/crm.postman.json", + "portmanConfigFile": "./config/portman-config.json", + "envFile": "./.env", + "includeTests": true, + "runNewman": true +} +``` + +```sh +portman --cliOptionsFile ./portman-cli-options.json +``` + +## Testing Apideck APIs + +```sh +# Test CRM API +portman -u https://specs.apideck.com/crm.yml -c ./portman-config.json -b https://unify.apideck.com -n true + +# Test Accounting API +portman -u https://specs.apideck.com/accounting.yml -c ./portman-config.json -b https://unify.apideck.com -n true +``` + +## CLI Reference + +| Flag | Description | +|------|-------------| +| `-l, --local` | Path to local OpenAPI spec | +| `-u, --url` | URL of remote OpenAPI spec | +| `-b, --baseUrl` | Override base URL | +| `-o, --output` | Output file path | +| `-c, --portmanConfigFile` | Path to portman-config | +| `-n, --runNewman` | Run Newman after generation | +| `-t, --includeTests` | Include test suite (default: true) | +| `-d, --newmanIterationData` | Path to iteration data | +| `--envFile` | Path to .env file | +| `--syncPostman` | Upload to Postman app | +| `--bundleContractTests` | Separate folder for contract tests | +| `--cliOptionsFile` | Path to CLI options file | +| `--init` | Interactive config wizard | diff --git a/providers/claude/plugin/skills/apideck-python/SKILL.md b/providers/claude/plugin/skills/apideck-python/SKILL.md new file mode 100644 index 0000000..85c3496 --- /dev/null +++ b/providers/claude/plugin/skills/apideck-python/SKILL.md @@ -0,0 +1,245 @@ +--- +name: apideck-python +description: Apideck Unified API integration patterns for Python. 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 using Python. Covers the apideck-unify SDK, authentication, CRUD operations, pagination, filtering, async support, and Vault connection management. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +# Apideck Python SDK Skill + +## Overview + +The [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 Python SDK (`apideck-unify`) provides typed clients for all unified APIs. + +## Installation + +```sh +pip install apideck-unify +``` + +Requires Python 3.9+. Dependencies: `httpx`, `pydantic`. + +## IMPORTANT RULES + +- ALWAYS use the `apideck-unify` SDK. DO NOT make raw `httpx`/`requests` calls to the Apideck API. +- ALWAYS pass `api_key`, `app_id`, and `consumer_id` when initializing the client. +- ALWAYS set the `APIDECK_API_KEY` environment variable rather than hardcoding API keys. +- USE `service_id` to specify which downstream connector to use (e.g., `"salesforce"`, `"quickbooks"`). If a consumer has multiple connections for an API, `service_id` is required. +- USE context managers (`with` / `async with`) for client lifecycle management. +- USE the `fields` parameter to request only the columns you need. +- USE the `filter_` parameter (note the trailing underscore) to narrow results server-side. +- ALWAYS handle errors with try/except using `models.ApideckError` as the base class. + +## Quick Start + +```python +from apideck_unify import Apideck +import os + +with Apideck( + api_key=os.getenv("APIDECK_API_KEY", ""), + app_id="your-app-id", + consumer_id="your-consumer-id", +) as apideck: + res = apideck.crm.contacts.list( + service_id="salesforce", + limit=20, + filter_={"email": "john@example.com"}, + ) + while res is not None: + for contact in res.data: + print(contact.name, contact.emails) + res = res.next() +``` + +## SDK Patterns + +### Client Setup + +```python +from apideck_unify import Apideck +import os + +with Apideck( + api_key=os.getenv("APIDECK_API_KEY", ""), + app_id="your-app-id", + consumer_id="your-consumer-id", +) as apideck: + # Make API calls here + pass +``` + +The `consumer_id` 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}()`. + +```python +import apideck_unify +from apideck_unify import Apideck +import os + +with Apideck( + api_key=os.getenv("APIDECK_API_KEY", ""), + app_id="your-app-id", + consumer_id="your-consumer-id", +) as apideck: + + # LIST - retrieve multiple records + res = apideck.crm.contacts.list( + service_id="salesforce", + limit=20, + filter_={"email": "john@example.com", "company_id": "12345"}, + sort={"by": apideck_unify.ContactsSortBy.CREATED_AT, "direction": apideck_unify.SortDirection.DESC}, + fields="id,name,email", + ) + + # CREATE - create a new record + res = apideck.crm.contacts.create( + service_id="salesforce", + first_name="John", + last_name="Doe", + emails=[{"email": "john@example.com", "type": apideck_unify.EmailType.PRIMARY}], + phone_numbers=[{"number": "+1234567890", "type": apideck_unify.PhoneNumberType.PRIMARY}], + ) + print(res.create_contact_response) + + # GET - retrieve a single record + res = apideck.crm.contacts.get(id="contact_123", service_id="salesforce") + + # UPDATE - modify an existing record + res = apideck.crm.contacts.update(id="contact_123", service_id="salesforce", first_name="Jane") + + # DELETE - remove a record + res = apideck.crm.contacts.delete(id="contact_123", service_id="salesforce") +``` + +### Pagination + +Use the `.next()` method on response objects for cursor-based pagination: + +```python +res = apideck.accounting.invoices.list(service_id="quickbooks", limit=50) + +while res is not None: + for invoice in res.data: + print(invoice.number, invoice.total) + res = res.next() +``` + +### Async Support + +Every sync method has an `_async` counterpart. Use `async with` as context manager: + +```python +import asyncio +from apideck_unify import Apideck +import os + +async def main(): + async with Apideck( + api_key=os.getenv("APIDECK_API_KEY", ""), + app_id="your-app-id", + consumer_id="your-consumer-id", + ) as apideck: + res = await apideck.crm.contacts.list_async( + service_id="salesforce", + limit=20, + ) + while res is not None: + for contact in res.data: + print(contact.name) + res = res.next() + +asyncio.run(main()) +``` + +### Error Handling + +```python +from apideck_unify import Apideck, models + +try: + res = apideck.crm.contacts.get(id="invalid", service_id="salesforce") +except models.BadRequestResponse as e: + print("Bad request:", e.message, e.status_code) +except models.UnauthorizedResponse as e: + print("Invalid API key or missing credentials") +except models.NotFoundResponse as e: + print("Record not found") +except models.PaymentRequiredResponse as e: + print("API limit reached") +except models.UnprocessableResponse as e: + print("Validation error:", e.message) +except models.ApideckError as e: + print(f"API error {e.status_code}: {e.message}") +``` + +All exceptions inherit from `models.ApideckError` with properties: `message`, `status_code`, `headers`, `body`, `raw_response`. + +### Common Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `service_id` | `str` | Downstream connector ID (e.g., `"quickbooks"`, `"salesforce"`) | +| `limit` | `int` | Max results per page (1-200, default 20) | +| `cursor` | `str` | Pagination cursor from previous response | +| `filter_` | `dict` | Resource-specific filter criteria (note trailing underscore) | +| `sort` | `dict` | `{"by": SortField, "direction": SortDirection}` | +| `fields` | `str` | Comma-separated field names to return | +| `pass_through` | `dict` | Pass-through query parameters for the downstream API | +| `raw` | `bool` | Include raw downstream response when `True` | +| `retry_config` | `RetryConfig` | Per-call retry override | + +### Pass-Through Parameters + +```python +# Query pass-through +res = apideck.accounting.invoices.list( + service_id="quickbooks", + pass_through={"search": "overdue"}, +) + +# Body pass-through for connector-specific fields +res = apideck.crm.contacts.create( + service_id="salesforce", + first_name="John", + last_name="Doe", + pass_through=[{ + "service_id": "salesforce", + "operation_id": "contactsAdd", + "extend_object": {"custom_sf_field__c": "value"}, + }], +) +``` + +### Retry Configuration + +```python +from apideck_unify import Apideck +from apideck_unify.utils import BackoffStrategy, RetryConfig + +with Apideck( + api_key=os.getenv("APIDECK_API_KEY", ""), + app_id="your-app-id", + consumer_id="your-consumer-id", + retry_config=RetryConfig("backoff", BackoffStrategy(1, 50, 1.1, 100), False), +) as apideck: + pass +``` + +## API Namespaces + +| Namespace | Resources | +|-----------|-----------| +| `apideck.accounting.*` | invoices, bills, payments, customers, suppliers, ledger_accounts, journal_entries, tax_rates, credit_notes, purchase_orders, balance_sheet, profit_and_loss, expenses, attachments, and more | +| `apideck.crm.*` | contacts, companies, leads, opportunities, activities, notes, pipelines, users | +| `apideck.hris.*` | employees, companies, departments, payrolls, time_off_requests | +| `apideck.file_storage.*` | files, folders, drives, shared_links, upload_sessions | +| `apideck.ats.*` | applicants, applications, jobs | +| `apideck.vault.*` | connections, consumers, sessions, custom_mappings, logs | +| `apideck.webhook.*` | webhooks, event_logs | diff --git a/providers/claude/plugin/skills/apideck-rest/SKILL.md b/providers/claude/plugin/skills/apideck-rest/SKILL.md new file mode 100644 index 0000000..3b54bc8 --- /dev/null +++ b/providers/claude/plugin/skills/apideck-rest/SKILL.md @@ -0,0 +1,301 @@ +--- +name: apideck-rest +description: Apideck Unified REST API reference for any language. 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 using direct HTTP calls. Covers authentication headers, CRUD operations, cursor-based pagination, filtering, sorting, error handling, rate limiting, pass-through parameters, and webhooks. Language-agnostic — works with curl, fetch, axios, httpx, or any HTTP client. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +# Apideck REST API Skill + +## Overview + +The [Apideck Unified API](https://apideck.com) provides a single REST endpoint to connect with 200+ third-party services across accounting, CRM, HRIS, file storage, ATS, e-commerce, and more. This skill covers direct HTTP usage for any language. + +**Base URL:** `https://unify.apideck.com` + +## IMPORTANT RULES + +- ALWAYS include the three required headers: `Authorization`, `x-apideck-app-id`, and `x-apideck-consumer-id`. +- ALWAYS make API calls server-side to prevent token leakage. +- USE `x-apideck-service-id` to specify which downstream connector to use. Required when a consumer has multiple connections for the same API. +- USE cursor-based pagination — iterate until `meta.cursors.next` is `null`. +- USE the `filter` query parameters to narrow results server-side. DO NOT fetch all records and filter client-side. +- USE the `fields` query parameter to request only the columns you need. +- DO NOT store API keys in source code. Use environment variables. + +## Authentication + +Every request requires these headers: + +| Header | Required | Description | +|--------|----------|-------------| +| `Authorization` | Yes | `Bearer {API_KEY}` | +| `x-apideck-app-id` | Yes | Your Apideck application ID | +| `x-apideck-consumer-id` | Yes | End-user/customer ID stored in Vault | +| `x-apideck-service-id` | No | Downstream connector ID (e.g., `salesforce`, `quickbooks`) | +| `Content-Type` | Yes (POST/PATCH) | `application/json` | + +## CRUD Operations + +All resources follow a consistent URL pattern: + +``` +GET /{api}/{resource} → List +POST /{api}/{resource} → Create +GET /{api}/{resource}/{id} → Get +PATCH /{api}/{resource}/{id} → Update +DELETE /{api}/{resource}/{id} → Delete +``` + +### List + +```bash +curl -X GET 'https://unify.apideck.com/crm/contacts?limit=20&filter[email]=john@example.com&sort[by]=updated_at&sort[direction]=desc&fields=id,name,email' \ + -H 'Authorization: Bearer {API_KEY}' \ + -H 'x-apideck-app-id: {APP_ID}' \ + -H 'x-apideck-consumer-id: {CONSUMER_ID}' \ + -H 'x-apideck-service-id: salesforce' +``` + +Response: +```json +{ + "status_code": 200, + "status": "OK", + "service": "salesforce", + "resource": "contacts", + "operation": "all", + "data": [ + { "id": "contact_123", "name": "John Doe", "email": "john@example.com" } + ], + "meta": { + "items_on_page": 20, + "cursors": { + "previous": null, + "current": "em9oby1jcm06Om9mZnNldDo6MA==", + "next": "em9oby1jcm06Om9mZnNldDo6MjA=" + } + }, + "links": { + "previous": null, + "current": "https://unify.apideck.com/crm/contacts?cursor=...", + "next": "https://unify.apideck.com/crm/contacts?cursor=..." + } +} +``` + +### Create + +```bash +curl -X POST 'https://unify.apideck.com/crm/contacts' \ + -H 'Authorization: Bearer {API_KEY}' \ + -H 'Content-Type: application/json' \ + -H 'x-apideck-app-id: {APP_ID}' \ + -H 'x-apideck-consumer-id: {CONSUMER_ID}' \ + -H 'x-apideck-service-id: salesforce' \ + -d '{ + "first_name": "John", + "last_name": "Doe", + "title": "VP of Engineering", + "emails": [{"email": "john@example.com", "type": "primary"}], + "phone_numbers": [{"number": "+1234567890", "type": "mobile"}], + "addresses": [{ + "type": "primary", + "street_1": "123 Main St", + "city": "San Francisco", + "state": "CA", + "postal_code": "94105", + "country": "US" + }] + }' +``` + +Response: `201 Created` with `{ "data": { "id": "contact_123" } }` + +### Get + +```bash +curl -X GET 'https://unify.apideck.com/crm/contacts/contact_123' \ + -H 'Authorization: Bearer {API_KEY}' \ + -H 'x-apideck-app-id: {APP_ID}' \ + -H 'x-apideck-consumer-id: {CONSUMER_ID}' \ + -H 'x-apideck-service-id: salesforce' +``` + +### Update + +```bash +curl -X PATCH 'https://unify.apideck.com/crm/contacts/contact_123' \ + -H 'Authorization: Bearer {API_KEY}' \ + -H 'Content-Type: application/json' \ + -H 'x-apideck-app-id: {APP_ID}' \ + -H 'x-apideck-consumer-id: {CONSUMER_ID}' \ + -H 'x-apideck-service-id: salesforce' \ + -d '{"title": "CTO"}' +``` + +### Delete + +```bash +curl -X DELETE 'https://unify.apideck.com/crm/contacts/contact_123' \ + -H 'Authorization: Bearer {API_KEY}' \ + -H 'x-apideck-app-id: {APP_ID}' \ + -H 'x-apideck-consumer-id: {CONSUMER_ID}' \ + -H 'x-apideck-service-id: salesforce' +``` + +## Pagination + +Apideck uses cursor-based pagination. Pass the `next` cursor from the response to fetch subsequent pages: + +| Parameter | Type | Default | Range | +|-----------|------|---------|-------| +| `limit` | integer | 20 | 1-200 | +| `cursor` | string | — | Opaque cursor from `meta.cursors.next` | + +```bash +# First page +curl 'https://unify.apideck.com/crm/contacts?limit=50' -H '...' + +# Next page +curl 'https://unify.apideck.com/crm/contacts?limit=50&cursor=em9oby1jcm06Om9mZnNldDo6NTA=' -H '...' +``` + +When `meta.cursors.next` is `null`, you have reached the last page. + +## Filtering and Sorting + +### Filters + +``` +?filter[field_name]=value +``` + +Available filters vary by resource. Common examples: + +| Resource | Filters | +|----------|---------| +| CRM Contacts | `filter[name]`, `filter[email]`, `filter[phone_number]`, `filter[company_id]`, `filter[owner_id]`, `filter[first_name]`, `filter[last_name]` | +| CRM Opportunities | `filter[status]`, `filter[title]`, `filter[company_id]`, `filter[owner_id]` | +| Accounting Invoices | `filter[updated_since]` (ISO 8601 datetime) | +| General | `filter[updated_since]` for incremental sync | + +### Sorting + +``` +?sort[by]=updated_at&sort[direction]=desc +``` + +### Field Selection + +``` +?fields=id,name,email,phone_numbers +``` + +## Pass-Through Parameters + +For connector-specific query parameters not in the unified model: + +``` +?pass_through[search]=overdue +``` + +For connector-specific fields in request bodies: + +```json +{ + "first_name": "John", + "pass_through": [ + { + "service_id": "salesforce", + "operation_id": "contactsAdd", + "extend_object": { + "custom_sf_field__c": "value" + } + } + ] +} +``` + +## Error Handling + +All errors follow this format: + +```json +{ + "status_code": 400, + "error": "Bad Request", + "type_name": "RequestValidationError", + "message": "Human-readable error description", + "detail": "Parameter-specific info", + "ref": "https://developers.apideck.com/errors#requestvalidationerror" +} +``` + +| Code | Meaning | +|------|---------| +| 400 | Bad Request — invalid parameters | +| 401 | Unauthorized — invalid API key | +| 402 | Payment Required — API limit reached | +| 404 | Not Found — resource does not exist | +| 422 | Unprocessable Entity — validation error | +| 429 | Too Many Requests — rate limit exceeded | +| 5xx | Server Error — Apideck or downstream failure | + +## Rate Limiting + +Apideck normalizes downstream rate limit headers: + +| Header | Description | +|--------|-------------| +| `x-downstream-ratelimit-limit` | Total request capacity | +| `x-downstream-ratelimit-remaining` | Remaining requests | +| `x-downstream-ratelimit-reset` | Unix timestamp when limits reset | + +## Raw Mode + +Append `?raw=true` to include the unmodified downstream response in a `_raw` property alongside normalized data. + +## Available API Endpoints + +| API | URL Prefix | Resources | +|-----|-----------|-----------| +| CRM | `/crm/` | contacts, companies, leads, opportunities, activities, notes, pipelines, users | +| Accounting | `/accounting/` | invoices, bills, payments, customers, suppliers, ledger-accounts, journal-entries, tax-rates, credit-notes, purchase-orders, balance-sheet, profit-and-loss | +| HRIS | `/hris/` | employees, companies, departments, payrolls, time-off-requests | +| File Storage | `/file-storage/` | files, folders, drives, drive-groups, shared-links, upload-sessions | +| ATS | `/ats/` | applicants, applications, jobs | +| Vault | `/vault/` | connections, sessions, consumers, custom-mappings, logs | +| Webhook | `/webhook/` | webhooks, event-logs | + +## Webhook Events + +Events follow the pattern `{api}.{resource}.{action}`: + +``` +crm.contact.created / .updated / .deleted +accounting.invoice.created / .updated / .deleted +hris.employee.created / .updated / .deleted / .terminated +file-storage.file.created / .updated / .deleted +ats.applicant.created / .updated / .deleted +``` + +Payload: +```json +{ + "payload": { + "event_type": "crm.contact.updated", + "unified_api": "crm", + "service_id": "salesforce", + "consumer_id": "user_abc123", + "entity_id": "contact_123", + "entity_type": "contact", + "occurred_at": "2024-06-15T10:30:00.000Z" + } +} +``` + +Verify signatures using the `x-apideck-signature` header with HMAC-SHA256. diff --git a/providers/cursor/plugin/.cursor-plugin/plugin.json b/providers/cursor/plugin/.cursor-plugin/plugin.json new file mode 100644 index 0000000..1cd1555 --- /dev/null +++ b/providers/cursor/plugin/.cursor-plugin/plugin.json @@ -0,0 +1,13 @@ +{ + "name": "apideck", + "description": "Apideck Unified API development plugin for Cursor", + "version": "1.0.0", + "author": { + "name": "Apideck", + "url": "https://apideck.com" + }, + "homepage": "https://developers.apideck.com", + "repository": "https://github.com/apideck/api-skills", + "license": "Apache-2.0", + "keywords": ["apideck", "unified-api", "integrations", "accounting", "crm", "hris", "ats", "file-storage"] +} diff --git a/providers/cursor/plugin/commands/list-connectors.md b/providers/cursor/plugin/commands/list-connectors.md new file mode 100644 index 0000000..71815c6 --- /dev/null +++ b/providers/cursor/plugin/commands/list-connectors.md @@ -0,0 +1,18 @@ +--- +description: List available Apideck connectors and their supported operations for a unified API +argument-hint: [api] (e.g., crm, accounting, hris) +--- + +# List Apideck Connectors + +Show available connectors and their capabilities for a given Apideck unified API: + +1. Accept the unified API name from arguments (e.g., `crm`, `accounting`, `hris`, `file-storage`, `ats`) +2. Use the Vault API to list connections: `vault.connections.list({ api: "" })` +3. Display each connector with: + - Service ID and name + - Connection state (available, callable, added, authorized, invalid) + - Whether it's currently enabled +4. If the user has no connections, suggest opening Vault to set them up +5. For each connected service, note which CRUD operations are supported +6. Include a link to the full connector coverage at https://developers.apideck.com/apis/connector/reference diff --git a/providers/cursor/plugin/commands/portman-init.md b/providers/cursor/plugin/commands/portman-init.md new file mode 100644 index 0000000..e46bf86 --- /dev/null +++ b/providers/cursor/plugin/commands/portman-init.md @@ -0,0 +1,21 @@ +--- +description: Generate a Portman configuration for API contract testing against an Apideck spec +argument-hint: [api] (e.g., crm, accounting, hris) +--- + +# Initialize Portman for Apideck API Testing + +Generate a complete Portman configuration for testing an Apideck unified API: + +1. Accept the API name from arguments (e.g., `crm`, `accounting`, `hris`, `file-storage`, `ats`) +2. Create a `portman-config.json` with: + - Contract tests enabled for all operations (status, content-type, JSON body, schema validation) + - Response time tests at 500ms threshold + - Variable chaining: capture `data.id` from create operations for use in get/update/delete + - Security overwrites for Bearer token from `{{bearerToken}}` env var + - Header overwrites for `x-apideck-app-id`, `x-apideck-consumer-id`, `x-apideck-service-id` + - A variation test for 401 Unauthorized with an invalid token + - An integration test for the primary resource lifecycle (create → get → update → delete) +3. Create a `.env` file template with `PORTMAN_BEARER_TOKEN`, `PORTMAN_APP_ID`, `PORTMAN_CONSUMER_ID`, `PORTMAN_SERVICE_ID` +4. Create a `portman-cli-options.json` pointing to the Apideck spec URL (`https://specs.apideck.com/{api}.yml`) +5. Show the command to run: `portman --cliOptionsFile ./portman-cli-options.json` diff --git a/providers/cursor/plugin/commands/test-connection.md b/providers/cursor/plugin/commands/test-connection.md new file mode 100644 index 0000000..3f43968 --- /dev/null +++ b/providers/cursor/plugin/commands/test-connection.md @@ -0,0 +1,22 @@ +--- +description: Test an Apideck connection by listing resources from a specified API and connector +argument-hint: [api] [service_id] (e.g., crm salesforce) +--- + +# Test Apideck Connection + +Verify that an Apideck connection is working by making a simple list call: + +1. Accept the unified API name (e.g., `crm`, `accounting`, `hris`) and service ID (e.g., `salesforce`, `quickbooks`) from arguments +2. Use the appropriate SDK to make a list call with `limit: 1` to the primary resource for that API: + - `crm` → list contacts + - `accounting` → list invoices + - `hris` → list employees + - `file-storage` → list files + - `ats` → list jobs +3. If successful, show the connection status, service name, and a sample record +4. If it fails, explain the error and provide troubleshooting steps: + - 401: Check API key, App ID, and Consumer ID + - 402: Check API plan limits + - 404: Verify the service ID and that the connection is authorized in Vault +5. Suggest using Vault to manage the connection if authorization is needed diff --git a/providers/cursor/plugin/skills/apideck-best-practices/SKILL.md b/providers/cursor/plugin/skills/apideck-best-practices/SKILL.md new file mode 100644 index 0000000..91fc2b9 --- /dev/null +++ b/providers/cursor/plugin/skills/apideck-best-practices/SKILL.md @@ -0,0 +1,176 @@ +--- +name: apideck-best-practices +description: Best practices for building Apideck integrations. Covers authentication patterns, pagination, error handling, connection management with Vault, webhook setup, and common pitfalls. Use when designing or reviewing any Apideck integration regardless of language. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +The Apideck Unified API base URL is `https://unify.apideck.com`. All API calls must be made server-side to prevent token leakage. + +## Authentication + +Every API call requires three headers: `Authorization: Bearer {API_KEY}`, `x-apideck-app-id`, and `x-apideck-consumer-id`. The `x-apideck-service-id` header specifies which downstream connector to use (e.g., `salesforce`, `quickbooks`, `xero`). When a consumer has multiple connections for the same unified API, `x-apideck-service-id` is required. + +Never hardcode API keys in source code. Always use environment variables or a secrets manager. Never expose API keys to the client/browser. + +## SDK Selection + +Always use the official Apideck SDK for the user's language. Do not make raw HTTP calls when an SDK is available: + +| Language | Package | +|----------|---------| +| TypeScript/Node.js | `@apideck/unify` | +| Python | `apideck-unify` | +| C# / .NET | `ApideckUnifySdk` | +| Java | `com.apideck:unify` | +| Go | `github.com/apideck-libraries/sdk-go` | +| PHP | `apideck-libraries/sdk-php` | + +All SDKs follow the same CRUD pattern: `client.{api}.{resource}.{operation}()`. All SDKs support retry configuration with exponential backoff. + +## Consumer ID Architecture + +The `consumerId` represents your end-user — the person whose third-party connections you're accessing. In multi-tenant SaaS applications: + +- Create one Apideck consumer per customer/tenant +- Store the mapping between your user IDs and Apideck consumer IDs +- Pass the correct `consumerId` per request — never use a shared consumer for all users +- Use [Vault sessions](https://developers.apideck.com/guides/vault) to let each user manage their own connections + +## Connection Management + +Always use [Apideck Vault](https://developers.apideck.com/guides/vault) for managing end-user connections. Never build custom OAuth flows when Vault handles them. + +Use [`@apideck/vault-js`](https://www.npmjs.com/package/@apideck/vault-js) to embed the connection management modal in your frontend. Session creation must always happen server-side: + +1. Server-side: Create a Vault session via `vault.sessions.create()` with the consumer's metadata +2. Client-side: Open the modal with `ApideckVault.open({ token })` +3. Handle `onConnectionChange` callbacks to update your UI when users authorize/modify connections + +Customize the Vault modal appearance via session `theme` properties (logo, colors, vault name) to match your brand. + +## Pagination + +Apideck uses cursor-based pagination across all list endpoints. Always paginate — never assume a single page returns all records. + +- Set `limit` (1-200, default 20) to control page size +- Use the SDK's built-in pagination: `for await...of` (Node.js), `.next()` (Python/Go/.NET), `callAsStream()` (Java), `foreach` generator (PHP) +- Stop when the next cursor is `null` + +For incremental sync, use `filter[updated_since]` with an ISO 8601 timestamp to fetch only records modified since your last sync. + +## Filtering and Field Selection + +Always filter server-side using the `filter` parameter. Never fetch all records and filter client-side — this wastes API units and increases response time. + +Always use the `fields` parameter to request only the columns you need. This reduces response size and improves performance. Example: `fields=id,name,email,updated_at`. + +## Error Handling + +Always handle errors. All SDKs provide typed error classes: + +| HTTP Code | Meaning | Action | +|-----------|---------|--------| +| 400 | Bad Request | Fix request parameters | +| 401 | Unauthorized | Check API key and consumer credentials | +| 402 | Payment Required | API limit reached — upgrade plan or wait | +| 404 | Not Found | Resource does not exist or wrong service ID | +| 422 | Unprocessable | Validation error — check required fields | +| 429 | Rate Limited | Back off and retry (check `x-downstream-ratelimit-reset` header) | +| 5xx | Server Error | Retry with exponential backoff | + +For downstream connector errors, inspect the `detail` and `downstream_errors` fields to get the original error from the third-party service. + +## Pass-Through for Connector-Specific Fields + +When the unified model doesn't cover a connector-specific field, use `pass_through` in the request body: + +```json +{ + "first_name": "John", + "pass_through": [ + { + "service_id": "salesforce", + "operation_id": "contactsAdd", + "extend_object": { "custom_sf_field__c": "value" } + } + ] +} +``` + +Use [custom field mapping](https://developers.apideck.com/guides/custom-mapping) in Vault to let end-users map their connector-specific fields without code changes. + +## Webhooks + +Use Apideck webhooks for real-time notifications instead of polling. Apideck supports both native webhooks (from connectors that support them) and virtual webhooks (polling-based for connectors that don't). + +Always verify webhook signatures using the `x-apideck-signature` header with HMAC-SHA256. Never process unverified webhook payloads. + +Webhook events follow the pattern `{api}.{resource}.{action}` (e.g., `crm.contact.created`, `accounting.invoice.updated`). + +## Logs + +Apideck provides detailed API call logs for every request made through the platform. Logs are available both in the Apideck dashboard and via the API. Use logs to debug failed requests, inspect downstream responses, and monitor integration health. + +Access logs programmatically via the Vault API: `vault.logs.list()`. Each log entry includes the HTTP method, URL, status code, request/response bodies, downstream service, and timestamps. + +Use logs when: + +- Debugging why a specific API call failed — inspect the downstream request and response +- Monitoring integration health — track error rates per connector +- Auditing API usage — review which consumers and services are being called +- Troubleshooting data mapping — compare the unified request with the downstream payload + +## Raw Mode + +Append `raw=true` to any request to include the unmodified downstream response alongside the normalized data. Use this for debugging or when you need connector-specific fields not in the unified model. + +## Testing with Portman + +Use [Portman](https://github.com/apideck-libraries/portman) to generate API contract tests from Apideck's OpenAPI specs. Apideck publishes specs at `https://specs.apideck.com/{api-name}.yml`. See the `apideck-portman` skill for full configuration. + +## Developer Tools + +### API Explorer + +The [Apideck API Explorer](https://developers.apideck.com/api-explorer) lets you test any unified API endpoint directly in the browser without writing code. It accepts a JWT token for authentication and returns live responses. + +The Explorer URL format supports pre-filled headers for quick access: + +``` +https://developers.apideck.com/api-explorer?id={api}&headers={encoded_json} +``` + +Where `headers` is a URL-encoded JSON object with: +- `Authorization`: `Bearer {JWT_TOKEN}` +- `x-apideck-auth-type`: `JWT` +- `x-apideck-app-id`: your app ID +- `x-apideck-consumer-id`: the consumer ID to test with + +Recommend the API Explorer when users want to: + +- Quickly verify a connection works before writing integration code +- Explore available fields and response shapes for a resource +- Debug unexpected API responses by comparing with the Explorer output +- Test filter and sort parameters interactively +- Share pre-configured API calls with teammates via URL + +### OpenAPI Specs + +Apideck publishes OpenAPI 3.x specs for all unified APIs at `https://specs.apideck.com/{api-name}.yml`. Use these for: + +- Generating typed clients with code generators +- Contract testing with Portman +- Importing into Postman, Insomnia, or other API tools +- Understanding the complete request/response schema + +## Common Pitfalls + +- Do not assume all connectors support all operations. Check [connector API coverage](https://developers.apideck.com/apis/connector/reference) before building. +- Do not mix `serviceId` values within a single workflow — stick to one connector per operation chain. +- Do not ignore the `row_version` field on updates — use it for optimistic concurrency when supported. +- Do not build retry logic on top of the SDK — all SDKs handle retries for transient errors automatically. +- Do not store Apideck data permanently — Apideck has zero data retention. Use it as a pass-through layer. diff --git a/providers/cursor/plugin/skills/apideck-codegen/SKILL.md b/providers/cursor/plugin/skills/apideck-codegen/SKILL.md new file mode 100644 index 0000000..bdd9f1b --- /dev/null +++ b/providers/cursor/plugin/skills/apideck-codegen/SKILL.md @@ -0,0 +1,258 @@ +--- +name: apideck-codegen +description: Generate typed API clients from Apideck OpenAPI specs using code generators. Use when the user wants to generate custom SDK clients, typed models, API stubs, or server scaffolding from Apideck's published OpenAPI specifications. Covers openapi-generator, Speakeasy, and Postman import workflows. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +# Apideck Code Generation Skill + +## Overview + +Apideck publishes OpenAPI 3.x specifications for all unified APIs at `https://specs.apideck.com/{api-name}.yml`. These specs can be used with code generators to produce typed clients, models, server stubs, and documentation in any language. + +## IMPORTANT RULES + +- ALWAYS prefer the official Apideck SDKs over generated clients. Code generation is for languages/frameworks not covered by official SDKs, or for custom model generation. +- ALWAYS use the latest spec from `https://specs.apideck.com/{api-name}.yml`. Do not use cached or outdated specs. +- USE the official SDKs for: TypeScript (`@apideck/unify`), Python (`apideck-unify`), C# (`ApideckUnifySdk`), Java (`com.apideck:unify`), Go (`sdk-go`), PHP (`sdk-php`). +- When generating clients, always configure authentication headers: `Authorization`, `x-apideck-app-id`, `x-apideck-consumer-id`. + +## Available OpenAPI Specs + +| API | Spec URL | +|-----|----------| +| Accounting | `https://specs.apideck.com/accounting.yml` | +| CRM | `https://specs.apideck.com/crm.yml` | +| HRIS | `https://specs.apideck.com/hris.yml` | +| File Storage | `https://specs.apideck.com/file-storage.yml` | +| ATS | `https://specs.apideck.com/ats.yml` | +| E-commerce | `https://specs.apideck.com/ecommerce.yml` | +| Issue Tracking | `https://specs.apideck.com/issue-tracking.yml` | +| SMS | `https://specs.apideck.com/sms.yml` | +| Lead | `https://specs.apideck.com/lead.yml` | +| Vault | `https://specs.apideck.com/vault.yml` | +| Webhook | `https://specs.apideck.com/webhook.yml` | +| Connector | `https://specs.apideck.com/connector.yml` | + +All specs are also available on GitHub: `https://github.com/apideck-libraries/openapi-specs` + +## OpenAPI Generator + +### Installation + +```sh +# npm +npm install @openapitools/openapi-generator-cli -g + +# Homebrew +brew install openapi-generator + +# Docker +docker pull openapitools/openapi-generator-cli +``` + +### Generate a Typed Client + +```sh +# TypeScript (Axios) +openapi-generator-cli generate \ + -i https://specs.apideck.com/crm.yml \ + -g typescript-axios \ + -o ./generated/crm-client \ + --additional-properties=npmName=apideck-crm,supportsES6=true,withInterfaces=true + +# Python (urllib3) +openapi-generator-cli generate \ + -i https://specs.apideck.com/accounting.yml \ + -g python \ + -o ./generated/accounting-client \ + --additional-properties=packageName=apideck_accounting + +# Rust +openapi-generator-cli generate \ + -i https://specs.apideck.com/crm.yml \ + -g rust \ + -o ./generated/crm-client \ + --additional-properties=packageName=apideck_crm + +# Swift +openapi-generator-cli generate \ + -i https://specs.apideck.com/hris.yml \ + -g swift5 \ + -o ./generated/hris-client \ + --additional-properties=projectName=ApideckHRIS + +# Kotlin +openapi-generator-cli generate \ + -i https://specs.apideck.com/crm.yml \ + -g kotlin \ + -o ./generated/crm-client \ + --additional-properties=groupId=com.apideck,artifactId=crm-client + +# Dart +openapi-generator-cli generate \ + -i https://specs.apideck.com/crm.yml \ + -g dart \ + -o ./generated/crm-client +``` + +### Generate Models Only + +When you only need typed data models without the API client: + +```sh +openapi-generator-cli generate \ + -i https://specs.apideck.com/crm.yml \ + -g typescript-axios \ + -o ./generated/crm-models \ + --global-property=models + +openapi-generator-cli generate \ + -i https://specs.apideck.com/accounting.yml \ + -g python \ + -o ./generated/accounting-models \ + --global-property=models +``` + +### Generate API Documentation + +```sh +# HTML docs +openapi-generator-cli generate \ + -i https://specs.apideck.com/crm.yml \ + -g html2 \ + -o ./docs/crm + +# Markdown docs +openapi-generator-cli generate \ + -i https://specs.apideck.com/crm.yml \ + -g markdown \ + -o ./docs/crm +``` + +### Available Generators + +Run `openapi-generator-cli list` for all generators. Common choices: + +| Language | Client Generator | Notes | +|----------|-----------------|-------| +| TypeScript | `typescript-axios`, `typescript-fetch`, `typescript-node` | `typescript-axios` recommended | +| Python | `python`, `python-pydantic-v1` | `python` uses urllib3 | +| Rust | `rust` | Uses reqwest | +| Swift | `swift5`, `swift6` | Native URLSession | +| Kotlin | `kotlin`, `kotlin-server` | Uses OkHttp | +| Dart | `dart`, `dart-dio` | `dart-dio` for Flutter | +| Ruby | `ruby` | Uses Faraday | +| Elixir | `elixir` | Uses Tesla | +| C++ | `cpp-restsdk` | Uses cpprestsdk | + +### Configuration File + +For repeatable generation, use a config file: + +```yaml +# openapitools.json +{ + "$schema": "node_modules/@openapitools/openapi-generator-cli/config.schema.json", + "spaces": 2, + "generator-cli": { + "version": "7.0.0", + "generators": { + "crm-ts": { + "generatorName": "typescript-axios", + "inputSpec": "https://specs.apideck.com/crm.yml", + "output": "./generated/crm", + "additionalProperties": { + "npmName": "apideck-crm", + "supportsES6": true, + "withInterfaces": true + } + }, + "accounting-py": { + "generatorName": "python", + "inputSpec": "https://specs.apideck.com/accounting.yml", + "output": "./generated/accounting", + "additionalProperties": { + "packageName": "apideck_accounting" + } + } + } + } +} +``` + +Then run: `openapi-generator-cli generate` + +## Speakeasy + +[Speakeasy](https://www.speakeasy.com) is the generator Apideck uses for their official SDKs. + +```sh +# Install +brew install speakeasy-api/homebrew-tap/speakeasy + +# Generate TypeScript SDK +speakeasy generate sdk \ + -s https://specs.apideck.com/crm.yml \ + -t typescript \ + -o ./generated/crm-sdk + +# Generate Python SDK +speakeasy generate sdk \ + -s https://specs.apideck.com/accounting.yml \ + -t python \ + -o ./generated/accounting-sdk + +# Generate Go SDK +speakeasy generate sdk \ + -s https://specs.apideck.com/hris.yml \ + -t go \ + -o ./generated/hris-sdk +``` + +## Postman Import + +Import Apideck specs directly into Postman for manual testing: + +```sh +# Using Portman (recommended — adds contract tests) +npx @apideck/portman -u https://specs.apideck.com/crm.yml -o ./crm.postman.json + +# Direct Postman import +# 1. Open Postman → Import → Link +# 2. Paste: https://specs.apideck.com/crm.yml +# 3. Postman auto-converts the OpenAPI spec to a collection +``` + +## Filtering Specs + +To generate a client for only a subset of endpoints, use `oas-format-filter` (used internally by Portman): + +```sh +npm install -g @apideck/oas-format-filter + +# Filter to only CRM contacts endpoints +oas-format-filter \ + --input https://specs.apideck.com/crm.yml \ + --output ./filtered-crm.yml \ + --filter '{"paths": ["/crm/contacts*"]}' +``` + +Then generate from the filtered spec. + +## Authentication Configuration + +When configuring generated clients, set these headers on every request: + +``` +Authorization: Bearer {APIDECK_API_KEY} +x-apideck-app-id: {APP_ID} +x-apideck-consumer-id: {CONSUMER_ID} +x-apideck-service-id: {SERVICE_ID} (optional, but recommended) +``` + +Base URL: `https://unify.apideck.com` diff --git a/providers/cursor/plugin/skills/apideck-connector-coverage/SKILL.md b/providers/cursor/plugin/skills/apideck-connector-coverage/SKILL.md new file mode 100644 index 0000000..d691b75 --- /dev/null +++ b/providers/cursor/plugin/skills/apideck-connector-coverage/SKILL.md @@ -0,0 +1,185 @@ +--- +name: apideck-connector-coverage +description: Check Apideck connector API coverage before building integrations. Use when determining which operations a connector supports, comparing connector capabilities, or diagnosing why an API call fails with a specific connector. Teaches agents to query the Connector API for real-time coverage data. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +# Apideck Connector Coverage Skill + +## Overview + +Not all Apideck connectors support all operations. Before building an integration, always check connector coverage to avoid runtime errors. The Connector API provides real-time metadata about which operations each connector supports. + +## IMPORTANT RULES + +- ALWAYS check connector coverage before recommending an integration approach. Never assume a connector supports an operation. +- If an operation is not supported, suggest alternatives: a different connector, pass-through to the raw API, or a workaround using supported operations. +- USE the Connector API to verify coverage programmatically. Do not rely on hardcoded lists. +- When a user reports a `501 Not Implemented` or `UnsupportedOperationError`, check coverage first. + +## Checking Coverage + +### Using the SDK (TypeScript) + +```typescript +import { Apideck } from "@apideck/unify"; + +const apideck = new Apideck({ + apiKey: process.env["APIDECK_API_KEY"] ?? "", + appId: "your-app-id", + consumerId: "your-consumer-id", +}); + +// List all connectors for an API +const { data } = await apideck.connector.connectorResources.get({ + id: "crm", + resourceId: "contacts", +}); +// Returns coverage per connector: which operations are supported + +// Get specific connector details +const { data: connector } = await apideck.connector.connectors.get({ + id: "salesforce", +}); +// Returns: name, status, auth_type, supported_resources, supported_events +``` + +### Using the REST API + +```bash +# List connectors for a unified API +curl 'https://unify.apideck.com/connector/connectors?filter[unified_api]=crm' \ + -H 'Authorization: Bearer {API_KEY}' \ + -H 'x-apideck-app-id: {APP_ID}' + +# Get connector details including supported resources +curl 'https://unify.apideck.com/connector/connectors/salesforce' \ + -H 'Authorization: Bearer {API_KEY}' \ + -H 'x-apideck-app-id: {APP_ID}' + +# Get API resource coverage (which connectors support what) +curl 'https://unify.apideck.com/connector/apis/crm/resources/contacts' \ + -H 'Authorization: Bearer {API_KEY}' \ + -H 'x-apideck-app-id: {APP_ID}' +``` + +### Using the Vault API + +Check which connections a consumer has and their state: + +```typescript +const { data } = await apideck.vault.connections.list({ + api: "crm", +}); + +for (const conn of data) { + console.log(`${conn.serviceId}: ${conn.state} (enabled: ${conn.enabled})`); + // state: available | callable | added | authorized | invalid +} +``` + +## Coverage States + +Each operation on a connector has a coverage status: + +| Status | Meaning | +|--------|---------| +| `supported` | Fully implemented and tested | +| `beta` | Implemented but may have edge cases | +| `not_supported` | Not available for this connector | + +## Common Coverage Patterns + +### Accounting API + +| Operation | QuickBooks | Xero | NetSuite | Sage Intacct | FreshBooks | +|-----------|-----------|------|----------|--------------|------------| +| Invoices CRUD | Full | Full | Full | Full | Full | +| Bills CRUD | Full | Full | Full | Full | Partial | +| Payments | Full | Full | Full | Full | Full | +| Journal Entries | Full | Full | Full | Full | Limited | +| Balance Sheet | Full | Full | Full | Full | No | +| Tax Rates (read) | Full | Full | Full | Full | Full | + +### CRM API + +| Operation | Salesforce | HubSpot | Pipedrive | Zoho CRM | Close | +|-----------|-----------|---------|-----------|----------|-------| +| Contacts CRUD | Full | Full | Full | Full | Full | +| Companies CRUD | Full | Full | Full | Full | Full | +| Leads CRUD | Full | Full | Full | Full | Full | +| Opportunities | Full | Full | Full | Full | Full | +| Activities | Full | Full | Full | Full | Partial | +| Pipelines (read) | Full | Full | Full | Full | Full | + +### HRIS API + +| Operation | BambooHR | Workday | Personio | Gusto | Rippling | +|-----------|---------|---------|----------|-------|----------| +| Employees CRUD | Full | Full | Full | Full | Full | +| Departments | Full | Full | Full | Full | Full | +| Payrolls (read) | Full | Partial | Partial | Full | Full | +| Time-Off | Full | Full | Full | Full | Full | + +> These tables are approximate. Always verify with the Connector API for real-time accuracy. + +## Handling Unsupported Operations + +When an operation isn't supported for a connector: + +### 1. Use Pass-Through (Proxy API) + +Make direct calls to the downstream API through Apideck's proxy: + +```typescript +// Direct pass-through to the downstream API +const response = await fetch("https://unify.apideck.com/proxy", { + method: "POST", + headers: { + Authorization: `Bearer ${apiKey}`, + "x-apideck-app-id": appId, + "x-apideck-consumer-id": consumerId, + "x-apideck-service-id": "salesforce", + "x-apideck-downstream-url": "https://api.salesforce.com/services/data/v59.0/sobjects/CustomObject__c", + "x-apideck-downstream-method": "GET", + "Content-Type": "application/json", + }, +}); +``` + +### 2. Check for Alternative Resources + +Some connectors map the same data to different unified resources. For example, a "deal" in Pipedrive maps to "opportunities" in the unified CRM API. + +### 3. Suggest a Different Connector + +If the user's chosen connector doesn't support an operation, suggest alternatives that do. Use the Connector API to find connectors that support the specific operation. + +### 4. Feature Request + +If a critical operation is missing, users can request it at https://github.com/apideck-libraries or through the Apideck dashboard. + +## Connector Authentication Types + +| Auth Type | Description | Connectors | +|-----------|-------------|------------| +| `oauth2` | OAuth 2.0 flow (managed by Vault) | Most cloud SaaS (Salesforce, HubSpot, QuickBooks Online, etc.) | +| `apiKey` | API key authentication | Some self-hosted or simpler services | +| `basic` | Username/password | Legacy systems, on-premise | +| `custom` | Connector-specific auth | Varies | + +Vault handles all OAuth flows. Users authorize via the Vault modal — you never need to implement OAuth yourself. + +## Debugging Coverage Issues + +When an API call fails: + +1. **Check the error type** — `UnsupportedOperationError` or `501` means the operation isn't implemented +2. **Verify connection state** — Use `vault.connections.list()` to check the connection is `authorized` +3. **Check connector coverage** — Use the Connector API to verify the operation is supported +4. **Check field support** — Some connectors support an operation but not all fields. Missing fields return `null` +5. **Use raw mode** — Add `raw=true` to see the downstream response for debugging diff --git a/providers/cursor/plugin/skills/apideck-dotnet/SKILL.md b/providers/cursor/plugin/skills/apideck-dotnet/SKILL.md new file mode 100644 index 0000000..de4aba5 --- /dev/null +++ b/providers/cursor/plugin/skills/apideck-dotnet/SKILL.md @@ -0,0 +1,215 @@ +--- +name: apideck-dotnet +description: Apideck Unified API integration patterns for C# and .NET. 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 using .NET. Covers the ApideckUnifySdk NuGet package, authentication, CRUD operations, pagination, error handling, and Vault connection management. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +# Apideck .NET SDK Skill + +## Overview + +The [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 .NET SDK (`ApideckUnifySdk`) provides typed clients for all unified APIs. + +## Installation + +```sh +dotnet add package ApideckUnifySdk +``` + +## IMPORTANT RULES + +- ALWAYS use the `ApideckUnifySdk` NuGet package. DO NOT make raw `HttpClient` calls to the Apideck API. +- ALWAYS pass `apiKey`, `appId`, and `consumerId` when initializing the client. +- USE `ServiceId` to specify which downstream connector to use (e.g., `"salesforce"`, `"quickbooks"`). +- USE async/await for all API calls — all operations return `Task`. +- ALWAYS handle errors with try/catch using `BaseException` as the base class. +- DO NOT store API keys in source code. Use environment variables or a secrets manager. + +## Quick Start + +```csharp +using ApideckUnifySdk; +using ApideckUnifySdk.Models.Requests; + +var sdk = new Apideck( + consumerId: "your-consumer-id", + appId: "your-app-id", + apiKey: Environment.GetEnvironmentVariable("APIDECK_API_KEY") ?? "" +); + +var res = await sdk.Crm.Contacts.ListAsync(new CrmContactsAllRequest { + ServiceId = "salesforce", + Limit = 20, +}); + +while (res != null) +{ + foreach (var contact in res.GetContactsResponse?.Data ?? []) + { + Console.WriteLine($"{contact.Name} - {contact.Emails?.FirstOrDefault()?.Email}"); + } + res = await res.Next!(); +} +``` + +## SDK Patterns + +### Client Setup + +```csharp +using ApideckUnifySdk; + +var sdk = new Apideck( + consumerId: "your-consumer-id", + appId: "your-app-id", + apiKey: Environment.GetEnvironmentVariable("APIDECK_API_KEY") ?? "" +); +``` + +### CRUD Operations + +All resources follow the pattern: `sdk.{Api}.{Resource}.{Operation}Async(request)`. + +```csharp +using ApideckUnifySdk; +using ApideckUnifySdk.Models.Requests; +using ApideckUnifySdk.Models.Components; + +// LIST +var listRes = await sdk.Crm.Contacts.ListAsync(new CrmContactsAllRequest { + ServiceId = "salesforce", + Limit = 20, + Filter = new ContactsFilter { Email = "john@example.com" }, + Sort = new ContactsSort { + By = ContactsSortBy.UpdatedAt, + Direction = SortDirection.Desc, + }, +}); + +// CREATE +var createRes = await sdk.Crm.Contacts.CreateAsync(new CrmContactsAddRequest { + ServiceId = "salesforce", + Contact = new ContactInput { + FirstName = "John", + LastName = "Doe", + Emails = new List { + new Email { EmailAddress = "john@example.com", Type = EmailType.Primary }, + }, + PhoneNumbers = new List { + new PhoneNumber { Number = "+1234567890", Type = PhoneNumberType.Mobile }, + }, + }, +}); +Console.WriteLine(createRes.CreateContactResponse?.Data?.Id); + +// GET +var getRes = await sdk.Crm.Contacts.GetAsync(new CrmContactsOneRequest { + Id = "contact_123", + ServiceId = "salesforce", +}); + +// UPDATE +var updateRes = await sdk.Crm.Contacts.UpdateAsync(new CrmContactsUpdateRequest { + Id = "contact_123", + ServiceId = "salesforce", + Contact = new ContactInput { FirstName = "Jane" }, +}); + +// DELETE +await sdk.Crm.Contacts.DeleteAsync(new CrmContactsDeleteRequest { + Id = "contact_123", + ServiceId = "salesforce", +}); +``` + +### Pagination + +Use the `Next` method on the response. Returns `null` when no more pages: + +```csharp +var res = await sdk.Accounting.Invoices.ListAsync(new AccountingInvoicesAllRequest { + ServiceId = "quickbooks", + Limit = 50, +}); + +while (res != null) +{ + foreach (var invoice in res.GetInvoicesResponse?.Data ?? []) + { + Console.WriteLine($"{invoice.Number}: {invoice.Total}"); + } + res = await res.Next!(); +} +``` + +### Error Handling + +```csharp +using ApideckUnifySdk.Models.Errors; + +try +{ + var res = await sdk.Crm.Contacts.GetAsync(new CrmContactsOneRequest { Id = "invalid" }); +} +catch (BadRequestResponse e) +{ + Console.Error.WriteLine($"Bad request: {e.Message}"); +} +catch (UnauthorizedResponse e) +{ + Console.Error.WriteLine("Invalid API key or missing credentials"); +} +catch (NotFoundResponse e) +{ + Console.Error.WriteLine("Record not found"); +} +catch (PaymentRequiredResponse e) +{ + Console.Error.WriteLine("API limit reached"); +} +catch (UnprocessableResponse e) +{ + Console.Error.WriteLine($"Validation error: {e.Message}"); +} +catch (BaseException e) +{ + Console.Error.WriteLine($"API error: {e.Message}"); + Console.Error.WriteLine($"Status: {e.Response.StatusCode}"); +} +``` + +### Retry Configuration + +```csharp +var sdk = new Apideck( + retryConfig: new RetryConfig( + strategy: RetryConfig.RetryStrategy.BACKOFF, + backoff: new BackoffStrategy( + initialIntervalMs: 1L, + maxIntervalMs: 50L, + maxElapsedTimeMs: 100L, + exponent: 1.1 + ), + retryConnectionErrors: false + ), + consumerId: "your-consumer-id", + appId: "your-app-id", + apiKey: Environment.GetEnvironmentVariable("APIDECK_API_KEY") ?? "" +); +``` + +## API Namespaces + +| Namespace | Resources | +|-----------|-----------| +| `sdk.Accounting.*` | Invoices, Bills, Payments, Customers, Suppliers, LedgerAccounts, JournalEntries, TaxRates, CreditNotes, PurchaseOrders, BalanceSheet, ProfitAndLoss, and more | +| `sdk.Crm.*` | Contacts, Companies, Leads, Opportunities, Activities, Notes, Pipelines, Users | +| `sdk.Hris.*` | Employees, Companies, Departments, Payrolls, TimeOffRequests | +| `sdk.FileStorage.*` | Files, Folders, Drives, DriveGroups, SharedLinks, UploadSessions | +| `sdk.Ats.*` | Applicants, Applications, Jobs | +| `sdk.Vault.*` | Connections, Consumers, Sessions, CustomMappings, Logs | +| `sdk.Webhook.*` | Webhooks, EventLogs | diff --git a/providers/cursor/plugin/skills/apideck-go/SKILL.md b/providers/cursor/plugin/skills/apideck-go/SKILL.md new file mode 100644 index 0000000..57db168 --- /dev/null +++ b/providers/cursor/plugin/skills/apideck-go/SKILL.md @@ -0,0 +1,255 @@ +--- +name: apideck-go +description: Apideck Unified API integration patterns for Go. 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 using Go. Covers the github.com/apideck-libraries/sdk-go package, authentication, CRUD operations, pagination, error handling, and Vault connection management. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +# Apideck Go SDK Skill + +## Overview + +The [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 Go SDK provides typed clients for all unified APIs. + +## Installation + +```sh +go get github.com/apideck-libraries/sdk-go +``` + +## IMPORTANT RULES + +- ALWAYS use the `github.com/apideck-libraries/sdk-go` SDK. DO NOT make raw `net/http` calls to the Apideck API. +- ALWAYS pass security, app ID, and consumer ID via functional options when creating the client. +- USE `ServiceID` on requests to specify which downstream connector to use. +- ALWAYS check returned `error` values (idiomatic Go error handling). +- USE `sdkgo.Pointer()` helper for optional fields. +- DO NOT store API keys in source code. Use environment variables. + +## Quick Start + +```go +package main + +import ( + "context" + "fmt" + "log" + "os" + + sdkgo "github.com/apideck-libraries/sdk-go" + "github.com/apideck-libraries/sdk-go/models/components" + "github.com/apideck-libraries/sdk-go/models/operations" +) + +func main() { + ctx := context.Background() + + s := sdkgo.New( + sdkgo.WithConsumerID("your-consumer-id"), + sdkgo.WithAppID("your-app-id"), + sdkgo.WithSecurity(os.Getenv("APIDECK_API_KEY")), + ) + + res, err := s.Crm.Contacts.List(ctx, operations.CrmContactsAllRequest{ + ServiceID: sdkgo.Pointer("salesforce"), + Limit: sdkgo.Pointer(int64(20)), + }) + if err != nil { + log.Fatal(err) + } + + for _, contact := range res.GetContactsResponse.Data { + fmt.Println(contact.Name) + } +} +``` + +## SDK Patterns + +### Client Setup + +```go +import sdkgo "github.com/apideck-libraries/sdk-go" + +s := sdkgo.New( + sdkgo.WithConsumerID("your-consumer-id"), + sdkgo.WithAppID("your-app-id"), + sdkgo.WithSecurity(os.Getenv("APIDECK_API_KEY")), +) +``` + +### CRUD Operations + +All resources follow the pattern: `s.{Api}.{Resource}.{Operation}(ctx, request)`, returning `(response, error)`. + +```go +import ( + sdkgo "github.com/apideck-libraries/sdk-go" + "github.com/apideck-libraries/sdk-go/models/components" + "github.com/apideck-libraries/sdk-go/models/operations" +) + +ctx := context.Background() + +// LIST +res, err := s.Crm.Contacts.List(ctx, operations.CrmContactsAllRequest{ + ServiceID: sdkgo.Pointer("salesforce"), + Limit: sdkgo.Pointer(int64(20)), + Filter: &components.ContactsFilter{ + Email: sdkgo.Pointer("john@example.com"), + }, + Sort: &components.ContactsSort{ + By: (*components.ContactsSortBy)(sdkgo.Pointer("updated_at")), + Direction: (*components.SortDirection)(sdkgo.Pointer("desc")), + }, +}) + +// CREATE +res, err := s.Crm.Contacts.Create(ctx, operations.CrmContactsAddRequest{ + ServiceID: sdkgo.Pointer("salesforce"), + Contact: components.ContactInput{ + FirstName: sdkgo.Pointer("John"), + LastName: sdkgo.Pointer("Doe"), + Emails: []components.Email{ + {Email: sdkgo.Pointer("john@example.com"), Type: (*components.EmailType)(sdkgo.Pointer("primary"))}, + }, + PhoneNumbers: []components.PhoneNumber{ + {Number: sdkgo.Pointer("+1234567890"), Type: (*components.PhoneNumberType)(sdkgo.Pointer("mobile"))}, + }, + }, +}) + +// GET +res, err := s.Crm.Contacts.Get(ctx, operations.CrmContactsOneRequest{ + ID: "contact_123", + ServiceID: sdkgo.Pointer("salesforce"), +}) + +// UPDATE +res, err := s.Crm.Contacts.Update(ctx, operations.CrmContactsUpdateRequest{ + ID: "contact_123", + ServiceID: sdkgo.Pointer("salesforce"), + Contact: components.ContactInput{ + FirstName: sdkgo.Pointer("Jane"), + }, +}) + +// DELETE +res, err := s.Crm.Contacts.Delete(ctx, operations.CrmContactsDeleteRequest{ + ID: "contact_123", + ServiceID: sdkgo.Pointer("salesforce"), +}) +``` + +### Pagination + +Use the `Next()` method on the response. Returns `nil` when no more pages: + +```go +res, err := s.Accounting.Invoices.List(ctx, operations.AccountingInvoicesAllRequest{ + ServiceID: sdkgo.Pointer("quickbooks"), + Limit: sdkgo.Pointer(int64(50)), +}) +if err != nil { + log.Fatal(err) +} + +for { + for _, invoice := range res.GetInvoicesResponse.Data { + fmt.Printf("%s: %v\n", *invoice.Number, *invoice.Total) + } + + res, err = res.Next() + if err != nil { + log.Fatal(err) + } + if res == nil { + break + } +} +``` + +### Error Handling + +```go +import ( + "errors" + "github.com/apideck-libraries/sdk-go/models/apierrors" +) + +res, err := s.Crm.Contacts.Get(ctx, req) +if err != nil { + var badReq *apierrors.BadRequestResponse + var unauthorized *apierrors.UnauthorizedResponse + var notFound *apierrors.NotFoundResponse + var paymentReq *apierrors.PaymentRequiredResponse + var unprocessable *apierrors.UnprocessableResponse + + switch { + case errors.As(err, &badReq): + log.Printf("Bad request: %s", badReq.Error()) + case errors.As(err, &unauthorized): + log.Printf("Unauthorized: %s", unauthorized.Error()) + case errors.As(err, ¬Found): + log.Printf("Not found: %s", notFound.Error()) + case errors.As(err, &paymentReq): + log.Printf("Payment required: %s", paymentReq.Error()) + case errors.As(err, &unprocessable): + log.Printf("Unprocessable: %s", unprocessable.Error()) + default: + var apiErr *apierrors.APIError + if errors.As(err, &apiErr) { + log.Printf("API error %d: %s", apiErr.StatusCode, apiErr.Error()) + } else { + log.Fatal(err) + } + } +} +``` + +### Retry Configuration + +```go +import "github.com/apideck-libraries/sdk-go/retry" + +// Global +s := sdkgo.New( + sdkgo.WithRetryConfig(retry.Config{ + Strategy: "backoff", + Backoff: &retry.BackoffStrategy{ + InitialInterval: 1, + MaxInterval: 50, + Exponent: 1.1, + MaxElapsedTime: 100, + }, + RetryConnectionErrors: false, + }), + sdkgo.WithConsumerID("your-consumer-id"), + sdkgo.WithAppID("your-app-id"), + sdkgo.WithSecurity(os.Getenv("APIDECK_API_KEY")), +) + +// Per-operation +res, err := s.Crm.Contacts.List(ctx, req, + operations.WithRetries(retry.Config{ + Strategy: "backoff", + Backoff: &retry.BackoffStrategy{InitialInterval: 1, MaxInterval: 50, Exponent: 1.1, MaxElapsedTime: 100}, + }), +) +``` + +## API Namespaces + +| Namespace | Resources | +|-----------|-----------| +| `s.Accounting.*` | Invoices, Bills, Payments, Customers, Suppliers, LedgerAccounts, JournalEntries, TaxRates, CreditNotes, PurchaseOrders, BalanceSheet, ProfitAndLoss, and more | +| `s.Crm.*` | Contacts, Companies, Leads, Opportunities, Activities, Notes, Pipelines, Users | +| `s.Hris.*` | Employees, Companies, Departments, Payrolls, TimeOffRequests | +| `s.FileStorage.*` | Files, Folders, Drives, DriveGroups, SharedLinks, UploadSessions | +| `s.Ats.*` | Applicants, Applications, Jobs | +| `s.Vault.*` | Connections, Consumers, Sessions, CustomMappings, Logs | +| `s.Webhook.*` | Webhooks, EventLogs | diff --git a/providers/cursor/plugin/skills/apideck-java/SKILL.md b/providers/cursor/plugin/skills/apideck-java/SKILL.md new file mode 100644 index 0000000..ad9979f --- /dev/null +++ b/providers/cursor/plugin/skills/apideck-java/SKILL.md @@ -0,0 +1,256 @@ +--- +name: apideck-java +description: Apideck Unified API integration patterns for Java. 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 using Java. Covers the com.apideck:unify Maven package, authentication, CRUD operations, pagination, async support, and Vault connection management. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +# Apideck Java SDK Skill + +## Overview + +The [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 Java SDK (`com.apideck:unify`) provides typed clients for all unified APIs. + +## Installation + +**Gradle:** +```groovy +implementation 'com.apideck:unify:0.30.3' +``` + +**Maven:** +```xml + + com.apideck + unify + 0.30.3 + +``` + +Requires JDK 11 or later. + +## IMPORTANT RULES + +- ALWAYS use the `com.apideck:unify` SDK. DO NOT make raw HTTP calls to the Apideck API. +- ALWAYS pass `apiKey`, `appId`, and `consumerId` when building the client. +- USE `serviceId` on requests to specify which downstream connector to use. +- USE the fluent builder pattern for constructing requests. +- ALWAYS handle errors with try/catch using `ApideckError` as the base class. +- DO NOT store API keys in source code. Use environment variables. + +## Quick Start + +```java +import com.apideck.unify.Apideck; +import com.apideck.unify.models.operations.*; + +Apideck sdk = Apideck.builder() + .consumerId("your-consumer-id") + .appId("your-app-id") + .apiKey(System.getenv("APIDECK_API_KEY")) + .build(); + +sdk.crm().contacts().list() + .serviceId("salesforce") + .limit(20) + .callAsStream() + .forEach(page -> { + page.getContactsResponse().ifPresent(res -> + res.getData().forEach(contact -> + System.out.println(contact.getName()) + ) + ); + }); +``` + +## SDK Patterns + +### Client Setup + +```java +import com.apideck.unify.Apideck; + +Apideck sdk = Apideck.builder() + .consumerId("your-consumer-id") + .appId("your-app-id") + .apiKey(System.getenv("APIDECK_API_KEY")) + .build(); +``` + +Async client: + +```java +import com.apideck.unify.AsyncApideck; + +AsyncApideck asyncSdk = Apideck.builder() + .consumerId("your-consumer-id") + .appId("your-app-id") + .apiKey(System.getenv("APIDECK_API_KEY")) + .build() + .async(); +``` + +### CRUD Operations + +Uses a fluent builder pattern: `sdk.{category}().{resource}().{operation}()`. + +```java +import com.apideck.unify.models.components.*; +import com.apideck.unify.models.operations.*; + +// LIST +sdk.crm().contacts().list() + .serviceId("salesforce") + .limit(20) + .filter(ContactsFilter.builder().email("john@example.com").build()) + .sort(ContactsSort.builder() + .by(ContactsSortBy.UPDATED_AT) + .direction(SortDirection.DESC) + .build()) + .call(); + +// CREATE +sdk.crm().contacts().create() + .serviceId("salesforce") + .contact(ContactInput.builder() + .firstName("John") + .lastName("Doe") + .emails(List.of(Email.builder() + .email("john@example.com") + .type(EmailType.PRIMARY) + .build())) + .phoneNumbers(List.of(PhoneNumber.builder() + .number("+1234567890") + .type(PhoneNumberType.MOBILE) + .build())) + .build()) + .call(); + +// GET +sdk.crm().contacts().get() + .id("contact_123") + .serviceId("salesforce") + .call(); + +// UPDATE +sdk.crm().contacts().update() + .id("contact_123") + .serviceId("salesforce") + .contact(ContactInput.builder().firstName("Jane").build()) + .call(); + +// DELETE +sdk.crm().contacts().delete() + .id("contact_123") + .serviceId("salesforce") + .call(); +``` + +### Pagination + +Multiple approaches available: + +```java +// Stream (recommended) +sdk.accounting().invoices().list() + .serviceId("quickbooks") + .limit(50) + .callAsStream() + .forEach(page -> { + // handle page + }); + +// Iterable +for (var page : sdk.accounting().invoices().list() + .serviceId("quickbooks") + .limit(50) + .callAsIterable()) { + // handle page +} + +// Reactive Streams (for Project Reactor, RxJava, etc.) +var publisher = sdk.accounting().invoices().list() + .serviceId("quickbooks") + .limit(50) + .callAsPublisher(); +``` + +### Async Support + +Returns `CompletableFuture` for standard operations: + +```java +AsyncApideck asyncSdk = sdk.async(); + +asyncSdk.crm().contacts().list() + .serviceId("salesforce") + .limit(20) + .call() + .thenAccept(res -> { + // handle response + }); +``` + +### Error Handling + +```java +import com.apideck.unify.models.errors.*; + +try { + sdk.crm().contacts().get() + .id("invalid") + .serviceId("salesforce") + .call(); +} catch (BadRequestResponse e) { + System.err.println("Bad request: " + e.message()); +} catch (UnauthorizedResponse e) { + System.err.println("Invalid API key"); +} catch (NotFoundResponse e) { + System.err.println("Record not found"); +} catch (PaymentRequiredResponse e) { + System.err.println("API limit reached"); +} catch (UnprocessableResponse e) { + System.err.println("Validation error: " + e.message()); +} catch (ApideckError e) { + System.err.println("API error " + e.code() + ": " + e.message()); +} +``` + +### Retry Configuration + +```java +import com.apideck.unify.utils.BackoffStrategy; +import com.apideck.unify.utils.RetryConfig; +import java.util.concurrent.TimeUnit; + +Apideck sdk = Apideck.builder() + .retryConfig(RetryConfig.builder() + .backoff(BackoffStrategy.builder() + .initialInterval(1L, TimeUnit.MILLISECONDS) + .maxInterval(50L, TimeUnit.MILLISECONDS) + .maxElapsedTime(1000L, TimeUnit.MILLISECONDS) + .baseFactor(1.1) + .jitterFactor(0.15) + .retryConnectError(false) + .build()) + .build()) + .consumerId("your-consumer-id") + .appId("your-app-id") + .apiKey(System.getenv("APIDECK_API_KEY")) + .build(); +``` + +## API Namespaces + +| Namespace | Resources | +|-----------|-----------| +| `sdk.accounting().*` | invoices, bills, payments, customers, suppliers, ledgerAccounts, journalEntries, taxRates, creditNotes, purchaseOrders, balanceSheet, profitAndLoss, and more | +| `sdk.crm().*` | contacts, companies, leads, opportunities, activities, notes, pipelines, users | +| `sdk.hris().*` | employees, companies, departments, payrolls, timeOffRequests | +| `sdk.fileStorage().*` | files, folders, drives, driveGroups, sharedLinks, uploadSessions | +| `sdk.ats().*` | applicants, applications, jobs | +| `sdk.vault().*` | connections, consumers, sessions, customMappings, logs | +| `sdk.webhook().*` | webhooks, eventLogs | diff --git a/providers/cursor/plugin/skills/apideck-migration/SKILL.md b/providers/cursor/plugin/skills/apideck-migration/SKILL.md new file mode 100644 index 0000000..efce36d --- /dev/null +++ b/providers/cursor/plugin/skills/apideck-migration/SKILL.md @@ -0,0 +1,262 @@ +--- +name: apideck-migration +description: Guide for migrating from direct third-party API integrations to Apideck's unified API. Use when a user wants to replace their existing Salesforce, HubSpot, QuickBooks, Xero, or other direct API integrations with Apideck, or when consolidating multiple integrations into a single unified layer. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +# Apideck Migration Guide Skill + +## Overview + +This skill helps migrate existing direct third-party API integrations (Salesforce, HubSpot, QuickBooks, Xero, etc.) to Apideck's unified API layer. The benefit is replacing N separate integrations with a single Apideck integration that supports 200+ connectors. + +## IMPORTANT RULES + +- ALWAYS check connector coverage before migrating. Not all operations may be supported through the unified API. +- ALWAYS preserve existing data flows and business logic. Migration should be transparent to end-users. +- USE `pass_through` for connector-specific fields that don't map to the unified model. +- USE custom field mapping in Vault for recurring connector-specific fields. +- NEVER delete the existing integration code until the Apideck migration is verified in production. +- RECOMMEND a phased migration: start with read operations, then writes, then webhooks. + +## Migration Strategy + +### Phase 1: Assessment + +1. **Inventory existing integrations** — List all third-party APIs currently in use +2. **Map operations** — For each integration, list the CRUD operations and fields used +3. **Check coverage** — Verify each operation is supported via the Apideck Connector API +4. **Identify gaps** — Note operations that need `pass_through` or Proxy API +5. **Plan consumer mapping** — Decide how your users/tenants map to Apideck consumer IDs + +### Phase 2: Connection Setup + +1. **Create Apideck account** — Get API key and App ID from the dashboard +2. **Enable connectors** — Enable the connectors you need in the Apideck dashboard +3. **Integrate Vault** — Add Vault JS to your frontend for user-managed connections +4. **Create consumers** — Map your existing users to Apideck consumer IDs +5. **Authorize connections** — Have users re-authorize via Vault (OAuth is handled automatically) + +### Phase 3: Read Migration (Low Risk) + +Replace read operations first since they're non-destructive: + +```typescript +// BEFORE: Direct Salesforce API +const contacts = await salesforce.sobjects.Contact.find({ + Email: "john@example.com", +}); + +// AFTER: Apideck Unified API +const { data } = await apideck.crm.contacts.list({ + serviceId: "salesforce", + filter: { email: "john@example.com" }, +}); +``` + +### Phase 4: Write Migration + +Replace create/update/delete operations: + +```typescript +// BEFORE: Direct HubSpot API +const contact = await hubspot.crm.contacts.basicApi.create({ + properties: { + firstname: "John", + lastname: "Doe", + email: "john@example.com", + company: "Acme Corp", + }, +}); + +// AFTER: Apideck Unified API +const { data } = await apideck.crm.contacts.create({ + serviceId: "hubspot", + contact: { + first_name: "John", + last_name: "Doe", + emails: [{ email: "john@example.com", type: "primary" }], + company_name: "Acme Corp", + }, +}); +``` + +### Phase 5: Webhook Migration + +Replace direct webhook handlers with Apideck's unified webhooks: + +```typescript +// BEFORE: Salesforce-specific webhook handler +app.post("/webhooks/salesforce", (req, res) => { + const event = req.body; + if (event.type === "ContactChangeEvent") { + handleContactChange(event); + } +}); + +// AFTER: Apideck unified webhook handler +app.post("/webhooks/apideck", (req, res) => { + const signature = req.headers["x-apideck-signature"]; + if (!verifySignature(req.body, signature, secret)) { + return res.status(401).send("Invalid signature"); + } + + const { event_type, entity_id, service_id } = req.body.payload; + // Works for ALL connectors, not just Salesforce + if (event_type === "crm.contact.updated") { + handleContactChange(entity_id, service_id); + } + res.status(200).send("OK"); +}); +``` + +## Common Migration Patterns + +### CRM: Salesforce to Apideck + +| Salesforce API | Apideck Unified API | +|---------------|---------------------| +| `sobjects.Contact.create()` | `crm.contacts.create({ serviceId: "salesforce" })` | +| `sobjects.Account.find()` | `crm.companies.list({ serviceId: "salesforce" })` | +| `sobjects.Opportunity.update()` | `crm.opportunities.update({ serviceId: "salesforce" })` | +| `sobjects.Lead.create()` | `crm.leads.create({ serviceId: "salesforce" })` | +| `sobjects.Task.create()` | `crm.activities.create({ serviceId: "salesforce" })` | +| Custom fields via `custom_sf_field__c` | `pass_through: [{ service_id: "salesforce", extend_object: { custom_sf_field__c: "value" } }]` | + +### CRM: HubSpot to Apideck + +| HubSpot API | Apideck Unified API | +|-------------|---------------------| +| `crm.contacts.basicApi.create()` | `crm.contacts.create({ serviceId: "hubspot" })` | +| `crm.companies.basicApi.getAll()` | `crm.companies.list({ serviceId: "hubspot" })` | +| `crm.deals.basicApi.create()` | `crm.opportunities.create({ serviceId: "hubspot" })` | +| `crm.contacts.searchApi.doSearch()` | `crm.contacts.list({ serviceId: "hubspot", filter: { ... } })` | + +### Accounting: QuickBooks to Apideck + +| QuickBooks API | Apideck Unified API | +|---------------|---------------------| +| `Invoice.create()` | `accounting.invoices.create({ serviceId: "quickbooks" })` | +| `Customer.findAll()` | `accounting.customers.list({ serviceId: "quickbooks" })` | +| `Bill.create()` | `accounting.bills.create({ serviceId: "quickbooks" })` | +| `Payment.create()` | `accounting.payments.create({ serviceId: "quickbooks" })` | +| `JournalEntry.create()` | `accounting.journalEntries.create({ serviceId: "quickbooks" })` | +| `CompanyInfo.get()` | `accounting.companyInfo.get({ serviceId: "quickbooks" })` | + +### Accounting: Xero to Apideck + +| Xero API | Apideck Unified API | +|----------|---------------------| +| `xero.accountingApi.createInvoices()` | `accounting.invoices.create({ serviceId: "xero" })` | +| `xero.accountingApi.getContacts()` | `accounting.customers.list({ serviceId: "xero" })` | +| `xero.accountingApi.createBankTransactions()` | `accounting.payments.create({ serviceId: "xero" })` | +| `xero.accountingApi.getReportBalanceSheet()` | `accounting.balanceSheet.get({ serviceId: "xero" })` | + +### HRIS: BambooHR to Apideck + +| BambooHR API | Apideck Unified API | +|-------------|---------------------| +| `GET /employees/directory` | `hris.employees.list({ serviceId: "bamboohr" })` | +| `POST /employees` | `hris.employees.create({ serviceId: "bamboohr" })` | +| `GET /employees/{id}` | `hris.employees.get({ serviceId: "bamboohr", id })` | +| `PUT /employees/{id}/time_off/request` | `hris.timeOffRequests.create({ serviceId: "bamboohr" })` | + +### File Storage: Google Drive to Apideck + +| Google Drive API | Apideck Unified API | +|-----------------|---------------------| +| `drive.files.list()` | `fileStorage.files.list({ serviceId: "google-drive" })` | +| `drive.files.create()` | `fileStorage.files.create({ serviceId: "google-drive" })` | +| `drive.files.get()` | `fileStorage.files.get({ serviceId: "google-drive" })` | +| `drive.files.export()` | `fileStorage.files.download({ serviceId: "google-drive" })` | +| `drive.permissions.create()` | `fileStorage.sharedLinks.create({ serviceId: "google-drive" })` | + +## Handling Connector-Specific Fields + +### Option 1: Pass-Through (inline) + +For one-off connector-specific fields in request bodies: + +```typescript +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: { + RecordTypeId: "012000000000001", + Custom_Score__c: 85, + }, + }, + ], + }, +}); +``` + +### Option 2: Custom Field Mapping (reusable) + +For fields that are used repeatedly, configure custom mapping in Vault so they appear as part of the unified model: + +```typescript +// Set up custom mapping via Vault API +await apideck.vault.customMappings.update({ + unifiedApi: "crm", + serviceId: "salesforce", + id: "mapping_123", + customMapping: { + value: "$.Custom_Score__c", + }, +}); + +// Now the field appears in custom_fields on every response +const { data } = await apideck.crm.contacts.get({ + id: "contact_123", + serviceId: "salesforce", +}); +// data.custom_fields includes { id: "mapping_123", value: 85 } +``` + +### Option 3: Proxy API (full control) + +For operations not supported by the unified API, use the Proxy to make direct downstream calls while still using Apideck's managed authentication: + +```typescript +const response = await fetch("https://unify.apideck.com/proxy", { + method: "POST", + headers: { + Authorization: `Bearer ${apiKey}`, + "x-apideck-app-id": appId, + "x-apideck-consumer-id": consumerId, + "x-apideck-service-id": "salesforce", + "x-apideck-downstream-url": "/services/data/v59.0/sobjects/CustomObject__c", + "x-apideck-downstream-method": "GET", + "Content-Type": "application/json", + }, +}); +``` + +## Testing the Migration + +1. **Run both integrations in parallel** — Shadow mode: make Apideck calls alongside existing calls and compare responses +2. **Use raw mode** — Add `raw=true` to Apideck calls to compare with the original API response +3. **Contract test with Portman** — Generate tests from OpenAPI specs and run against your staging environment +4. **Test with the API Explorer** — Use the [Apideck API Explorer](https://developers.apideck.com/api-explorer) to verify endpoints interactively +5. **Gradual rollout** — Migrate one connector at a time, starting with the lowest-traffic integration + +## Post-Migration Benefits + +Once migrated to Apideck: + +- **Add new connectors instantly** — Enable a new connector in the dashboard, no code changes needed +- **User self-service** — End-users manage their own connections via Vault +- **Unified webhooks** — One handler for all connectors instead of N separate handlers +- **Unified error handling** — One error format instead of learning each API's error structure +- **Automatic maintenance** — Apideck handles API version changes, deprecations, and auth token refresh diff --git a/providers/cursor/plugin/skills/apideck-node/SKILL.md b/providers/cursor/plugin/skills/apideck-node/SKILL.md new file mode 100644 index 0000000..b91e83b --- /dev/null +++ b/providers/cursor/plugin/skills/apideck-node/SKILL.md @@ -0,0 +1,276 @@ +--- +name: apideck-node +description: 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. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +# Apideck TypeScript SDK Skill + +## Overview + +The [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. + +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 + +```sh +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 + +```typescript +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 + +```typescript +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}()`. + +```typescript +// 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: + +```typescript +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: + +```typescript +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 + +```typescript +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`: + +```typescript +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: + +```typescript +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](references/accounting-api.md) | invoices, bills, payments, customers, suppliers, ledgerAccounts, journalEntries, taxRates, creditNotes, purchaseOrders, balanceSheet, profitAndLoss, and more | +| `apideck.crm.*` | [references/crm-api.md](references/crm-api.md) | contacts, companies, leads, opportunities, activities, notes, pipelines, users | +| `apideck.hris.*` | [references/hris-api.md](references/hris-api.md) | employees, companies, departments, payrolls, timeOffRequests | +| `apideck.fileStorage.*` | [references/file-storage-api.md](references/file-storage-api.md) | files, folders, drives, driveGroups, sharedLinks, uploadSessions | +| `apideck.ats.*` | [references/ats-api.md](references/ats-api.md) | applicants, applications, jobs | +| `apideck.vault.*` | [references/vault-api.md](references/vault-api.md) | connections, connectionSettings, consumers, customMappings, logs, sessions | +| `apideck.webhook.*` | [references/webhook-api.md](references/webhook-api.md) | webhooks, eventLogs | + +## Vault JS (Embeddable UI) + +Use [`@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. + +```typescript +// 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](references/vault-js.md) for full configuration options, theming, React integration, and event callbacks. diff --git a/providers/cursor/plugin/skills/apideck-node/references/accounting-api.md b/providers/cursor/plugin/skills/apideck-node/references/accounting-api.md new file mode 100644 index 0000000..5fb4ee1 --- /dev/null +++ b/providers/cursor/plugin/skills/apideck-node/references/accounting-api.md @@ -0,0 +1,349 @@ +# Accounting API Reference + +Base namespace: `apideck.accounting` + +Supported connectors: QuickBooks, Xero, NetSuite, Exact Online, FreshBooks, Sage Intacct, Sage Business Cloud, MYOB, Wave, Zoho Books, and 20+ more. + +## Invoices + +```typescript +// List invoices +const { data } = await apideck.accounting.invoices.list({ + serviceId: "quickbooks", + limit: 50, + filter: { updated_since: "2024-01-01T00:00:00.000Z" }, + sort: { by: "updated_at", direction: "desc" }, +}); + +// Create invoice +const { data } = await apideck.accounting.invoices.create({ + serviceId: "quickbooks", + invoice: { + type: "standard", + number: "INV-001", + customer: { id: "customer_123", display_name: "Acme Corp" }, + invoice_date: "2024-06-01", + due_date: "2024-07-01", + currency: "USD", + line_items: [ + { + description: "Consulting services", + quantity: 10, + unit_price: 150, + total_amount: 1500, + tax_rate: { id: "tax_rate_1" }, + }, + ], + }, +}); + +// Get invoice by ID +const { data } = await apideck.accounting.invoices.get({ + id: "invoice_123", + serviceId: "quickbooks", +}); + +// Update invoice +const { data } = await apideck.accounting.invoices.update({ + id: "invoice_123", + serviceId: "quickbooks", + invoice: { due_date: "2024-08-01" }, +}); + +// Delete invoice +await apideck.accounting.invoices.delete({ + id: "invoice_123", + serviceId: "quickbooks", +}); +``` + +Key invoice fields: `id`, `number`, `type` (`standard` | `credit`), `customer`, `invoice_date`, `due_date`, `currency`, `line_items[]`, `sub_total`, `total_tax`, `total`, `balance`, `status` (`draft` | `submitted` | `authorised` | `paid` | `voided`), `payment_method`, `tracking_categories[]`, `custom_fields[]`. + +## Bills + +```typescript +// List bills +const { data } = await apideck.accounting.bills.list({ + serviceId: "xero", + filter: { updated_since: "2024-01-01T00:00:00.000Z" }, +}); + +// Create bill +const { data } = await apideck.accounting.bills.create({ + serviceId: "xero", + bill: { + supplier: { id: "supplier_123" }, + bill_date: "2024-06-01", + due_date: "2024-07-01", + currency: "USD", + line_items: [ + { + description: "Office supplies", + quantity: 1, + unit_price: 250, + total_amount: 250, + }, + ], + }, +}); +``` + +## Payments + +```typescript +// List payments +const { data } = await apideck.accounting.payments.list({ + serviceId: "quickbooks", +}); + +// Create payment +const { data } = await apideck.accounting.payments.create({ + serviceId: "quickbooks", + payment: { + customer: { id: "customer_123" }, + total_amount: 1500, + currency: "USD", + payment_method: "credit_card", + reference: "PAY-001", + allocations: [{ id: "invoice_123", type: "invoice", amount: 1500 }], + }, +}); +``` + +## Customers + +```typescript +// List customers +const { data } = await apideck.accounting.customers.list({ + serviceId: "quickbooks", + filter: { company_name: "Acme", email: "billing@acme.com" }, +}); + +// Create customer +const { data } = await apideck.accounting.customers.create({ + serviceId: "quickbooks", + customer: { + display_name: "Acme Corp", + company_name: "Acme Corporation", + emails: [{ email: "billing@acme.com", type: "primary" }], + phone_numbers: [{ number: "+1234567890", type: "primary" }], + addresses: [ + { + type: "primary", + street_1: "123 Main St", + city: "San Francisco", + state: "CA", + postal_code: "94105", + country: "US", + }, + ], + }, +}); +``` + +## Suppliers + +```typescript +const { data } = await apideck.accounting.suppliers.list({ + serviceId: "xero", + filter: { company_name: "Vendor Inc" }, +}); + +const { data } = await apideck.accounting.suppliers.create({ + serviceId: "xero", + supplier: { + display_name: "Vendor Inc", + company_name: "Vendor Incorporated", + emails: [{ email: "ap@vendor.com", type: "primary" }], + }, +}); +``` + +## Ledger Accounts + +```typescript +// List chart of accounts +const { data } = await apideck.accounting.ledgerAccounts.list({ + serviceId: "quickbooks", + filter: { type: "expense" }, +}); + +// Create ledger account +const { data } = await apideck.accounting.ledgerAccounts.create({ + serviceId: "quickbooks", + ledgerAccount: { + display_id: "6000", + name: "Marketing Expenses", + type: "expense", + sub_type: "expense", + currency: "USD", + }, +}); +``` + +Ledger account types: `asset`, `liability`, `equity`, `income`, `expense`, `other`. + +## Journal Entries + +```typescript +const { data } = await apideck.accounting.journalEntries.create({ + serviceId: "quickbooks", + journalEntry: { + title: "Monthly depreciation", + currency: "USD", + line_items: [ + { + type: "debit", + ledger_account: { id: "account_depreciation" }, + total_amount: 500, + description: "Depreciation expense", + }, + { + type: "credit", + ledger_account: { id: "account_accumulated_dep" }, + total_amount: 500, + description: "Accumulated depreciation", + }, + ], + }, +}); +``` + +## Tax Rates (read-only) + +```typescript +const { data } = await apideck.accounting.taxRates.list({ + serviceId: "quickbooks", +}); + +const { data } = await apideck.accounting.taxRates.get({ + id: "tax_rate_1", + serviceId: "quickbooks", +}); +``` + +## Credit Notes + +```typescript +const { data } = await apideck.accounting.creditNotes.create({ + serviceId: "xero", + creditNote: { + number: "CN-001", + customer: { id: "customer_123" }, + currency: "USD", + line_items: [ + { description: "Refund for defective item", quantity: 1, unit_price: 100, total_amount: 100 }, + ], + }, +}); +``` + +## Purchase Orders + +```typescript +const { data } = await apideck.accounting.purchaseOrders.create({ + serviceId: "quickbooks", + purchaseOrder: { + po_number: "PO-001", + supplier: { id: "supplier_123" }, + line_items: [ + { description: "Laptop", quantity: 5, unit_price: 1200, total_amount: 6000 }, + ], + }, +}); +``` + +## Reports + +```typescript +// Balance sheet +const { data } = await apideck.accounting.balanceSheet.get({ + serviceId: "quickbooks", + filter: { start_date: "2024-01-01", end_date: "2024-12-31" }, +}); +// Returns: assets, liabilities, equity with nested account breakdowns + +// Profit & Loss +const { data } = await apideck.accounting.profitAndLoss.get({ + serviceId: "quickbooks", + filter: { start_date: "2024-01-01", end_date: "2024-12-31" }, +}); +// Returns: income, expenses, net_income/net_loss with category breakdowns + +// Aged Debtors +const { data } = await apideck.accounting.agedDebtors.get({ + serviceId: "quickbooks", +}); + +// Aged Creditors +const { data } = await apideck.accounting.agedCreditors.get({ + serviceId: "quickbooks", +}); +``` + +## Expenses + +```typescript +const { data } = await apideck.accounting.expenses.create({ + serviceId: "quickbooks", + expense: { + transaction_date: "2024-06-15", + account: { id: "account_123" }, + currency: "USD", + line_items: [ + { description: "Client dinner", total_amount: 150, account: { id: "account_meals" } }, + ], + }, +}); +``` + +## Bill Payments + +```typescript +const { data } = await apideck.accounting.billPayments.create({ + serviceId: "quickbooks", + billPayment: { + supplier: { id: "supplier_123" }, + total_amount: 250, + currency: "USD", + allocations: [{ id: "bill_123", type: "bill", amount: 250 }], + }, +}); +``` + +## Tracking Categories + +```typescript +const { data } = await apideck.accounting.trackingCategories.list({ + serviceId: "xero", +}); +// Returns categories like departments, projects, cost centers +``` + +## Attachments + +```typescript +// List attachments for a resource +const { data } = await apideck.accounting.attachments.list({ + referenceType: "invoice", + referenceId: "invoice_123", + serviceId: "quickbooks", +}); + +// Download attachment +const { data } = await apideck.accounting.attachments.download({ + referenceType: "invoice", + referenceId: "invoice_123", + id: "attachment_123", + serviceId: "quickbooks", +}); +``` + +## Company Info + +```typescript +const { data } = await apideck.accounting.companyInfo.get({ + serviceId: "quickbooks", +}); +// Returns: legal_name, company_name, currency, fiscal_year_start_month, addresses, phone_numbers +``` diff --git a/providers/cursor/plugin/skills/apideck-node/references/ats-api.md b/providers/cursor/plugin/skills/apideck-node/references/ats-api.md new file mode 100644 index 0000000..7647207 --- /dev/null +++ b/providers/cursor/plugin/skills/apideck-node/references/ats-api.md @@ -0,0 +1,121 @@ +# ATS API Reference + +Base namespace: `apideck.ats` + +Supported connectors: Greenhouse, Lever, Workable, Recruitee, Bullhorn, SAP SuccessFactors, Teamtailor, and more. + +## Jobs + +Jobs are read-only in the unified model. + +```typescript +// List jobs +const { data } = await apideck.ats.jobs.list({ + serviceId: "greenhouse", + limit: 50, +}); + +// Get job details +const { data } = await apideck.ats.jobs.get({ + id: "job_123", + serviceId: "greenhouse", +}); +``` + +Key job fields: `id`, `title`, `description`, `status` (`draft` | `open` | `closed`), `department`, `branch`, `recruiters[]`, `hiring_managers[]`, `addresses[]`, `confidential`, `salary`, `tags[]`, `created_at`, `updated_at`, `closing_date`. + +## Applicants + +```typescript +// List applicants +const { data } = await apideck.ats.applicants.list({ + serviceId: "greenhouse", + limit: 50, +}); + +// Create applicant +const { data } = await apideck.ats.applicants.create({ + serviceId: "greenhouse", + applicant: { + first_name: "Sarah", + last_name: "Chen", + emails: [{ email: "sarah@example.com", type: "primary" }], + phone_numbers: [{ number: "+1234567890", type: "mobile" }], + title: "Senior Software Engineer", + websites: [ + { url: "https://github.com/sarahchen", type: "github" }, + { url: "https://linkedin.com/in/sarahchen", type: "linkedin" }, + ], + social_links: [ + { url: "https://twitter.com/sarahchen", type: "twitter" }, + ], + tags: ["referral", "senior"], + }, +}); + +// Get applicant +const { data } = await apideck.ats.applicants.get({ + id: "applicant_123", + serviceId: "greenhouse", +}); + +// Update applicant +const { data } = await apideck.ats.applicants.update({ + id: "applicant_123", + serviceId: "greenhouse", + applicant: { + tags: ["referral", "senior", "fast-track"], + }, +}); + +// Delete applicant +await apideck.ats.applicants.delete({ + id: "applicant_123", + serviceId: "greenhouse", +}); +``` + +Key applicant fields: `id`, `first_name`, `last_name`, `name`, `title`, `emails[]`, `phone_numbers[]`, `addresses[]`, `websites[]`, `social_links[]`, `stage_id`, `recruiter_id`, `coordinator_id`, `applications[]`, `tags[]`, `sources[]`, `confidential`, `custom_fields[]`. + +## Applications + +```typescript +// List applications +const { data } = await apideck.ats.applications.list({ + serviceId: "greenhouse", +}); + +// Create application (link applicant to job) +const { data } = await apideck.ats.applications.create({ + serviceId: "greenhouse", + application: { + applicant_id: "applicant_123", + job_id: "job_456", + stage_id: "stage_phone_screen", + status: "open", + }, +}); + +// Get application +const { data } = await apideck.ats.applications.get({ + id: "application_123", + serviceId: "greenhouse", +}); + +// Update application (advance stage) +const { data } = await apideck.ats.applications.update({ + id: "application_123", + serviceId: "greenhouse", + application: { + stage_id: "stage_onsite_interview", + }, +}); + +// Delete application +await apideck.ats.applications.delete({ + id: "application_123", + serviceId: "greenhouse", +}); +``` + +Key application fields: `id`, `applicant_id`, `job_id`, `status` (`open` | `rejected` | `hired`), `stage`, `current_stage`, `reject_reason`, `source`, `custom_fields[]`, `created_at`, `updated_at`. diff --git a/providers/cursor/plugin/skills/apideck-node/references/crm-api.md b/providers/cursor/plugin/skills/apideck-node/references/crm-api.md new file mode 100644 index 0000000..764b3aa --- /dev/null +++ b/providers/cursor/plugin/skills/apideck-node/references/crm-api.md @@ -0,0 +1,273 @@ +# CRM API Reference + +Base namespace: `apideck.crm` + +Supported connectors: Salesforce, HubSpot, Pipedrive, Microsoft Dynamics 365, Zoho CRM, Close, Copper, Freshsales, and 15+ more. + +## Contacts + +```typescript +// List contacts with filtering and sorting +const { data } = await apideck.crm.contacts.list({ + serviceId: "salesforce", + limit: 50, + filter: { + name: "John", + email: "john@example.com", + phone_number: "+1234567890", + company_id: "company_123", + owner_id: "user_456", + first_name: "John", + last_name: "Doe", + }, + sort: { by: "updated_at", direction: "desc" }, +}); + +// Create contact +const { data } = await apideck.crm.contacts.create({ + serviceId: "salesforce", + contact: { + first_name: "John", + last_name: "Doe", + title: "VP of Engineering", + department: "Engineering", + company_id: "company_123", + emails: [ + { email: "john@example.com", type: "primary" }, + { email: "john.doe@personal.com", type: "secondary" }, + ], + phone_numbers: [ + { number: "+1234567890", type: "mobile" }, + ], + addresses: [ + { + type: "primary", + street_1: "123 Main St", + city: "San Francisco", + state: "CA", + postal_code: "94105", + country: "US", + }, + ], + social_links: [ + { url: "https://linkedin.com/in/johndoe", type: "linkedin" }, + ], + tags: ["vip", "enterprise"], + custom_fields: [ + { id: "lead_source", value: "Website" }, + ], + }, +}); + +// Get contact +const { data } = await apideck.crm.contacts.get({ + id: "contact_123", + serviceId: "salesforce", +}); + +// Update contact +const { data } = await apideck.crm.contacts.update({ + id: "contact_123", + serviceId: "salesforce", + contact: { title: "CTO" }, +}); + +// Delete contact +await apideck.crm.contacts.delete({ + id: "contact_123", + serviceId: "salesforce", +}); +``` + +Key contact fields: `id`, `name`, `first_name`, `last_name`, `title`, `department`, `company_id`, `company_name`, `emails[]`, `phone_numbers[]`, `addresses[]`, `social_links[]`, `tags[]`, `owner_id`, `lead_source`, `description`, `custom_fields[]`, `created_at`, `updated_at`. + +## Companies + +```typescript +// List companies +const { data } = await apideck.crm.companies.list({ + serviceId: "hubspot", + filter: { name: "Acme" }, + sort: { by: "name", direction: "asc" }, +}); + +// Create company +const { data } = await apideck.crm.companies.create({ + serviceId: "hubspot", + company: { + name: "Acme Corporation", + industry: "Technology", + website: "https://acme.com", + annual_revenue: "10000000", + number_of_employees: "500", + emails: [{ email: "info@acme.com", type: "primary" }], + phone_numbers: [{ number: "+1234567890", type: "primary" }], + addresses: [ + { + type: "primary", + street_1: "456 Tech Blvd", + city: "San Francisco", + state: "CA", + postal_code: "94105", + country: "US", + }, + ], + }, +}); +``` + +Key company fields: `id`, `name`, `industry`, `website`, `annual_revenue`, `number_of_employees`, `owner_id`, `emails[]`, `phone_numbers[]`, `addresses[]`, `social_links[]`, `tags[]`, `description`, `custom_fields[]`. + +## Leads + +```typescript +// List leads +const { data } = await apideck.crm.leads.list({ + serviceId: "salesforce", + filter: { email: "lead@example.com", name: "Jane" }, +}); + +// Create lead +const { data } = await apideck.crm.leads.create({ + serviceId: "salesforce", + lead: { + name: "Jane Smith", + first_name: "Jane", + last_name: "Smith", + company_name: "Startup Inc", + title: "CEO", + emails: [{ email: "jane@startup.com", type: "primary" }], + phone_numbers: [{ number: "+1987654321", type: "mobile" }], + lead_source: "Webinar", + monetary_amount: 50000, + currency: "USD", + }, +}); + +// Convert lead (connector-specific, use pass-through) +const { data } = await apideck.crm.leads.update({ + id: "lead_123", + serviceId: "salesforce", + lead: { + pass_through: [ + { + service_id: "salesforce", + extend_object: { Status: "Converted" }, + }, + ], + }, +}); +``` + +## Opportunities + +```typescript +// List opportunities +const { data } = await apideck.crm.opportunities.list({ + serviceId: "pipedrive", + filter: { status: "open" }, +}); + +// Create opportunity +const { data } = await apideck.crm.opportunities.create({ + serviceId: "pipedrive", + opportunity: { + title: "Enterprise deal - Acme Corp", + primary_contact_id: "contact_123", + company_id: "company_456", + pipeline_id: "pipeline_789", + stage_id: "stage_negotiation", + status: "open", + monetary_amount: 150000, + currency: "USD", + win_probability: 60, + close_date: "2024-09-30", + owner_id: "user_123", + }, +}); + +// Update opportunity stage +const { data } = await apideck.crm.opportunities.update({ + id: "opportunity_123", + serviceId: "pipedrive", + opportunity: { + stage_id: "stage_closed_won", + status: "won", + win_probability: 100, + }, +}); +``` + +Key opportunity fields: `id`, `title`, `primary_contact_id`, `company_id`, `pipeline_id`, `stage_id`, `status` (`open` | `won` | `lost`), `monetary_amount`, `currency`, `win_probability`, `close_date`, `owner_id`, `priority`, `tags[]`. + +## Activities + +```typescript +// List activities +const { data } = await apideck.crm.activities.list({ + serviceId: "salesforce", +}); + +// Create activity (call, meeting, email, task, etc.) +const { data } = await apideck.crm.activities.create({ + serviceId: "salesforce", + activity: { + type: "call", + title: "Follow-up call with Acme", + description: "Discuss contract renewal", + activity_date: "2024-07-01T10:00:00.000Z", + duration_seconds: 1800, + owner_id: "user_123", + contact_id: "contact_456", + company_id: "company_789", + }, +}); +``` + +Activity types: `call`, `meeting`, `email`, `note`, `task`, `deadline`, `send_letter`, `send_quote`, `other`. + +## Notes + +```typescript +// List notes +const { data } = await apideck.crm.notes.list({ + serviceId: "hubspot", +}); + +// Create note +const { data } = await apideck.crm.notes.create({ + serviceId: "hubspot", + note: { + title: "Meeting summary", + content: "Discussed Q3 renewal. Client interested in premium tier.", + contact_id: "contact_123", + company_id: "company_456", + opportunity_id: "opportunity_789", + }, +}); +``` + +## Pipelines + +```typescript +// List pipelines +const { data } = await apideck.crm.pipelines.list({ + serviceId: "pipedrive", +}); +// Returns: id, name, stages[{ id, name, display_order }], currency + +// Get pipeline with stages +const { data } = await apideck.crm.pipelines.get({ + id: "pipeline_123", + serviceId: "pipedrive", +}); +``` + +## Users + +```typescript +const { data } = await apideck.crm.users.list({ + serviceId: "salesforce", +}); +// Returns CRM users: id, name, email, role, status +``` diff --git a/providers/cursor/plugin/skills/apideck-node/references/file-storage-api.md b/providers/cursor/plugin/skills/apideck-node/references/file-storage-api.md new file mode 100644 index 0000000..5bdd059 --- /dev/null +++ b/providers/cursor/plugin/skills/apideck-node/references/file-storage-api.md @@ -0,0 +1,196 @@ +# File Storage API Reference + +Base namespace: `apideck.fileStorage` + +Supported connectors: Box, Dropbox, Google Drive, OneDrive, SharePoint. + +## Files + +```typescript +// List files in a folder +const { data } = await apideck.fileStorage.files.list({ + serviceId: "google-drive", + filter: { + folder_id: "folder_123", + drive_id: "drive_456", + }, + sort: { by: "updated_at", direction: "desc" }, +}); + +// Search files +const { data } = await apideck.fileStorage.files.search({ + serviceId: "google-drive", + filesSearch: { + query: "quarterly report", + }, +}); + +// Get file metadata +const { data } = await apideck.fileStorage.files.get({ + id: "file_123", + serviceId: "google-drive", +}); + +// Upload a file +const { data } = await apideck.fileStorage.files.create({ + serviceId: "google-drive", + createFileRequest: { + name: "report.pdf", + parent_folder_id: "folder_123", + drive_id: "drive_456", + description: "Q3 financial report", + }, +}); + +// Download a file +const { data } = await apideck.fileStorage.files.download({ + id: "file_123", + serviceId: "google-drive", +}); + +// Update file metadata +const { data } = await apideck.fileStorage.files.update({ + id: "file_123", + serviceId: "google-drive", + updateFileRequest: { + name: "report-v2.pdf", + description: "Updated Q3 report", + }, +}); + +// Delete file +await apideck.fileStorage.files.delete({ + id: "file_123", + serviceId: "google-drive", +}); +``` + +Key file fields: `id`, `name`, `description`, `mime_type`, `size`, `parent_folders[]`, `owner`, `permissions[]`, `downloadable`, `exportable`, `created_at`, `updated_at`. + +## Folders + +```typescript +// Get folder contents +const { data } = await apideck.fileStorage.folders.get({ + id: "folder_123", + serviceId: "dropbox", +}); + +// Create folder +const { data } = await apideck.fileStorage.folders.create({ + serviceId: "dropbox", + createFolderRequest: { + name: "Project Documents", + parent_folder_id: "folder_root", + drive_id: "drive_123", + }, +}); + +// Update folder +const { data } = await apideck.fileStorage.folders.update({ + id: "folder_123", + serviceId: "dropbox", + updateFolderRequest: { + name: "Project Docs - Archived", + }, +}); + +// Copy folder +const { data } = await apideck.fileStorage.folders.copy({ + id: "folder_123", + serviceId: "dropbox", + copyFolderRequest: { + parent_folder_id: "folder_destination", + name: "Project Documents (Copy)", + }, +}); + +// Delete folder +await apideck.fileStorage.folders.delete({ + id: "folder_123", + serviceId: "dropbox", +}); +``` + +## Shared Links + +```typescript +// List shared links +const { data } = await apideck.fileStorage.sharedLinks.list({ + serviceId: "box", +}); + +// Create shared link +const { data } = await apideck.fileStorage.sharedLinks.create({ + serviceId: "box", + sharedLink: { + target: { id: "file_123", type: "file" }, + scope: "company", + password: "secure-password", + expires_at: "2024-12-31T23:59:59.000Z", + }, +}); + +// Delete shared link +await apideck.fileStorage.sharedLinks.delete({ + id: "link_123", + serviceId: "box", +}); +``` + +Shared link scopes: `public`, `company`, `password`. + +## Upload Sessions (large files) + +For files larger than the direct upload limit, use chunked upload sessions: + +```typescript +// 1. Create upload session +const { data: session } = await apideck.fileStorage.uploadSessions.create({ + serviceId: "box", + createUploadSessionRequest: { + name: "large-backup.zip", + parent_folder_id: "folder_123", + size: 104857600, // 100MB in bytes + }, +}); + +// 2. Upload parts (SDK handles chunking) +await apideck.fileStorage.uploadSessions.upload({ + id: session.id, + serviceId: "box", + // file part data +}); + +// 3. Finish upload session +const { data: file } = await apideck.fileStorage.uploadSessions.finish({ + id: session.id, + serviceId: "box", +}); +``` + +## Drives + +```typescript +// List drives +const { data } = await apideck.fileStorage.drives.list({ + serviceId: "google-drive", +}); + +// Create drive (shared drives) +const { data } = await apideck.fileStorage.drives.create({ + serviceId: "google-drive", + drive: { + name: "Engineering Shared Drive", + description: "Shared documents for the engineering team", + }, +}); +``` + +## Drive Groups (SharePoint only) + +```typescript +const { data } = await apideck.fileStorage.driveGroups.list({ + serviceId: "sharepoint", +}); +``` diff --git a/providers/cursor/plugin/skills/apideck-node/references/hris-api.md b/providers/cursor/plugin/skills/apideck-node/references/hris-api.md new file mode 100644 index 0000000..0b5d1e6 --- /dev/null +++ b/providers/cursor/plugin/skills/apideck-node/references/hris-api.md @@ -0,0 +1,212 @@ +# HRIS API Reference + +Base namespace: `apideck.hris` + +Supported connectors: Workday, BambooHR, Hibob, SAP SuccessFactors, Personio, Gusto, Rippling, Deel, ADP, Namely, Payfit, and 40+ more. + +## Employees + +```typescript +// List employees +const { data } = await apideck.hris.employees.list({ + serviceId: "bamboohr", + limit: 50, + filter: { + company_id: "company_123", + department_id: "dept_456", + employment_status: "active", + manager_id: "emp_789", + title: "Engineer", + }, + sort: { by: "last_name", direction: "asc" }, +}); + +// Create employee +const { data } = await apideck.hris.employees.create({ + serviceId: "bamboohr", + employee: { + first_name: "Alice", + last_name: "Johnson", + display_name: "Alice Johnson", + gender: "female", + birthday: "1990-05-15", + emails: [{ email: "alice@company.com", type: "work" }], + phone_numbers: [{ number: "+1234567890", type: "mobile" }], + addresses: [ + { + type: "primary", + street_1: "789 Oak Ave", + city: "Austin", + state: "TX", + postal_code: "73301", + country: "US", + }, + ], + employment_status: "active", + employment_role: { type: "employee", sub_type: "full_time" }, + department_id: "dept_engineering", + title: "Senior Software Engineer", + manager: { id: "emp_manager_123" }, + start_date: "2024-03-01", + compensations: [ + { + rate: 150000, + payment_unit: "year", + currency: "USD", + effective_date: "2024-03-01", + }, + ], + }, +}); + +// Get employee +const { data } = await apideck.hris.employees.get({ + id: "emp_123", + serviceId: "bamboohr", +}); + +// Update employee +const { data } = await apideck.hris.employees.update({ + id: "emp_123", + serviceId: "bamboohr", + employee: { + title: "Staff Software Engineer", + compensations: [ + { + rate: 175000, + payment_unit: "year", + currency: "USD", + effective_date: "2025-01-01", + }, + ], + }, +}); + +// Delete employee +await apideck.hris.employees.delete({ + id: "emp_123", + serviceId: "bamboohr", +}); +``` + +Key employee fields: `id`, `first_name`, `last_name`, `display_name`, `title`, `department_id`, `department`, `company_id`, `company_name`, `employment_status` (`active` | `inactive` | `terminated`), `employment_role`, `manager`, `start_date`, `termination_date`, `birthday`, `gender`, `emails[]`, `phone_numbers[]`, `addresses[]`, `jobs[]`, `compensations[]`, `teams[]`, `social_links[]`, `custom_fields[]`. + +Employment role types: `employee`, `contractor`. Sub-types: `full_time`, `part_time`, `intern`, `freelance`, `temp`, `seasonal`. + +## Departments + +```typescript +// List departments +const { data } = await apideck.hris.departments.list({ + serviceId: "bamboohr", +}); + +// Create department +const { data } = await apideck.hris.departments.create({ + serviceId: "bamboohr", + department: { + name: "Product Engineering", + code: "PROD-ENG", + parent_id: "dept_engineering", + }, +}); +``` + +## Companies + +```typescript +// List HRIS companies (for multi-entity organizations) +const { data } = await apideck.hris.companies.list({ + serviceId: "bamboohr", +}); + +// Get company details +const { data } = await apideck.hris.companies.get({ + id: "company_123", + serviceId: "bamboohr", +}); +// Returns: legal_name, display_name, status, addresses, phone_numbers, ein (tax ID) +``` + +## Payrolls + +```typescript +// List payrolls +const { data } = await apideck.hris.payrolls.list({ + serviceId: "gusto", + filter: { + start_date: "2024-01-01", + end_date: "2024-01-31", + }, +}); + +// Get specific payroll +const { data } = await apideck.hris.payrolls.get({ + payrollId: "payroll_123", + serviceId: "gusto", +}); +// Returns: id, processed_date, check_date, totals (gross_pay, net_pay, total_tax, total_deductions) +``` + +## Employee Payrolls + +```typescript +// List employee payrolls for a specific employee +const { data } = await apideck.hris.employeePayrolls.list({ + employeeId: "emp_123", + serviceId: "gusto", +}); +// Returns per-employee: gross_pay, net_pay, taxes[], deductions[], compensations[] +``` + +## Time-Off Requests + +```typescript +// List time-off requests +const { data } = await apideck.hris.timeOffRequests.list({ + serviceId: "bamboohr", + filter: { + employee_id: "emp_123", + start_date: "2024-06-01", + end_date: "2024-06-30", + }, +}); + +// Create time-off request +const { data } = await apideck.hris.timeOffRequests.create({ + serviceId: "bamboohr", + timeOffRequest: { + employee_id: "emp_123", + policy_id: "policy_pto", + start_date: "2024-07-15", + end_date: "2024-07-19", + status: "requested", + request_type: "vacation", + notes: { employee: "Family vacation" }, + }, +}); + +// Update time-off request (approve/deny) +const { data } = await apideck.hris.timeOffRequests.update({ + id: "time_off_123", + serviceId: "bamboohr", + timeOffRequest: { + status: "approved", + notes: { manager: "Approved. Enjoy!" }, + }, +}); +``` + +Time-off statuses: `requested`, `approved`, `declined`, `cancelled`, `deleted`. +Request types: `vacation`, `sick`, `personal`, `jury_duty`, `volunteer`, `bereavement`. + +## Employee Schedules + +```typescript +// List employee schedules +const { data } = await apideck.hris.employeeSchedules.list({ + employeeId: "emp_123", + serviceId: "bamboohr", +}); +// Returns weekly schedule patterns with work days and hours +``` diff --git a/providers/cursor/plugin/skills/apideck-node/references/vault-api.md b/providers/cursor/plugin/skills/apideck-node/references/vault-api.md new file mode 100644 index 0000000..2969ac9 --- /dev/null +++ b/providers/cursor/plugin/skills/apideck-node/references/vault-api.md @@ -0,0 +1,164 @@ +# Vault API Reference + +Base namespace: `apideck.vault` + +Vault manages authentication and connections between your application and downstream services. It provides embeddable UI components for end-users to authorize their own integrations. + +## Connections + +```typescript +// List all connections for a consumer +const { data } = await apideck.vault.connections.list({ + api: "crm", +}); +// Returns: id, service_id, name, state, auth_type, enabled, created_at + +// Get connection details +const { data } = await apideck.vault.connections.get({ + serviceId: "salesforce", + unifiedApi: "crm", +}); + +// Update connection settings +const { data } = await apideck.vault.connections.update({ + serviceId: "salesforce", + unifiedApi: "crm", + connection: { + enabled: true, + settings: { + instance_url: "https://mycompany.salesforce.com", + }, + }, +}); + +// Delete connection +await apideck.vault.connections.delete({ + serviceId: "salesforce", + unifiedApi: "crm", +}); + +// Import connection (bring existing OAuth tokens) +const { data } = await apideck.vault.connections.imports({ + serviceId: "salesforce", + unifiedApi: "crm", + connectionImportData: { + credentials: { + access_token: "existing-access-token", + refresh_token: "existing-refresh-token", + }, + }, +}); +``` + +Connection states: `available`, `callable`, `added`, `authorized`, `invalid`. + +## Connection Settings + +```typescript +// Get available settings for a connection +const { data } = await apideck.vault.connectionSettings.list({ + unifiedApi: "accounting", + serviceId: "quickbooks", + resource: "invoices", +}); +// Returns configurable fields, allowed values, and current settings +``` + +## Sessions + +Create a Vault session URL to embed the connection management UI: + +```typescript +// Create session for Vault embedded UI +const { data } = await apideck.vault.sessions.create({ + session: { + consumer_metadata: { + account_name: "Acme Corp", + user_name: "John Doe", + email: "john@acme.com", + image: "https://acme.com/john.jpg", + }, + redirect_uri: "https://myapp.com/integrations", + settings: { + unified_apis: ["accounting", "crm"], + hide_resource_settings: false, + sandbox_mode: false, + auto_redirect: false, + }, + theme: { + vault_name: "My App Integrations", + primary_color: "#4F46E5", + sidepanel_background_color: "#F9FAFB", + sidepanel_text_color: "#111827", + favicon: "https://myapp.com/favicon.ico", + logo: "https://myapp.com/logo.png", + }, + }, +}); +// data.session_uri -> redirect user here to manage connections +``` + +## Consumers + +```typescript +// List consumers +const { data } = await apideck.vault.consumers.list({ limit: 50 }); + +// Create consumer +const { data } = await apideck.vault.consumers.create({ + consumer: { + consumer_id: "user_abc123", + metadata: { + account_name: "Acme Corp", + user_name: "John Doe", + email: "john@acme.com", + }, + }, +}); + +// Get consumer +const { data } = await apideck.vault.consumers.get({ + consumerId: "user_abc123", +}); + +// Delete consumer and all their connections +await apideck.vault.consumers.delete({ + consumerId: "user_abc123", +}); +``` + +## Custom Mappings + +Map connector-specific fields to your unified model: + +```typescript +// List custom mappings for a connection +const { data } = await apideck.vault.customMappings.list({ + unifiedApi: "crm", + serviceId: "salesforce", +}); + +// Update custom field mapping +const { data } = await apideck.vault.customMappings.update({ + unifiedApi: "crm", + serviceId: "salesforce", + id: "mapping_123", + customMapping: { + value: "$.custom_sf_field__c", + }, +}); +``` + +## Logs + +```typescript +// List API call logs +const { data } = await apideck.vault.logs.list({ + filter: { + connector_id: "salesforce", + status_code: 200, + exclude_unified_apis: "vault,proxy", + }, +}); +// Returns: api_style, base_url, path, method, status_code, duration, consumer_id, service_id, timestamp +``` diff --git a/providers/cursor/plugin/skills/apideck-node/references/vault-js.md b/providers/cursor/plugin/skills/apideck-node/references/vault-js.md new file mode 100644 index 0000000..eb7e455 --- /dev/null +++ b/providers/cursor/plugin/skills/apideck-node/references/vault-js.md @@ -0,0 +1,293 @@ +# Vault JS Integration Reference + +Vault JS is a vanilla JavaScript library to embed Apideck Vault in any web application. It lets your users authorize connectors and manage integration settings through a pre-built modal UI, and stores credentials securely so you can make authorized API calls on their behalf. + +## Installation + +```bash +npm install @apideck/vault-js +``` + +Or load via CDN (available globally as `window.ApideckVault`): + +```html + +``` + +## Prerequisites + +Session creation MUST happen server-side to prevent token leakage. Create a session using the `@apideck/unify` SDK before opening Vault: + +```typescript +import { Apideck } from "@apideck/unify"; + +const apideck = new Apideck({ + apiKey: process.env["APIDECK_API_KEY"] ?? "", + appId: "your-app-id", + consumerId: "user_abc123", +}); + +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"], + hide_resource_settings: false, + sandbox_mode: false, + }, + theme: { + vault_name: "My App Integrations", + primary_color: "#4F46E5", + sidepanel_background_color: "#F9FAFB", + sidepanel_text_color: "#111827", + favicon: "https://myapp.com/favicon.ico", + logo: "https://myapp.com/logo.png", + }, + }, +}); + +const sessionToken = data.session_token; +// Pass this token to your frontend +``` + +## Opening Vault + +```typescript +import { ApideckVault } from "@apideck/vault-js"; + +// Basic usage — open Vault modal with a session token +ApideckVault.open({ + token: "SESSION_TOKEN_FROM_BACKEND", +}); +``` + +## Configuration Options + +```typescript +ApideckVault.open({ + // REQUIRED: JWT session token from your backend + token: "SESSION_TOKEN_FROM_BACKEND", + + // Show integrations for a single Unified API only + unifiedApi: "accounting", + + // Open Vault for a single integration only + serviceId: "quickbooks", + + // Initial view to display + // "settings" | "configurable-resources" | "custom-mapping" + initialView: "settings", + + // Locale for the modal UI + // "en" (default) | "nl" | "de" | "fr" | "es" + locale: "en", + + // Show language switch in the modal + showLanguageSwitch: true, + + // Use button layout instead of dropdown menus + showButtonLayout: false, + + // Automatically start OAuth flow when opening a connection + autoStartAuthorization: true, + + // Callback when modal is ready and visible + onReady: () => { + console.log("Vault modal is open"); + }, + + // Callback when modal is closed + onClose: () => { + console.log("Vault modal closed"); + }, + + // Callback when a connection is added, updated, or authorized + onConnectionChange: (connection) => { + console.log("Connection changed:", connection); + // connection: { id, service_id, unified_api, state, ... } + }, + + // Callback when a connection is deleted + onConnectionDelete: (connection) => { + console.log("Connection deleted:", connection); + }, +}); +``` + +## Closing Vault Programmatically + +```typescript +ApideckVault.close(); +``` + +## Full Integration Example + +### Server-side (Node.js / Express) + +```typescript +import express from "express"; +import { Apideck } from "@apideck/unify"; + +const app = express(); + +const apideck = new Apideck({ + apiKey: process.env["APIDECK_API_KEY"] ?? "", + appId: process.env["APIDECK_APP_ID"] ?? "", + consumerId: "", // set per-request +}); + +// Create a Vault session for the authenticated user +app.post("/api/vault/session", async (req, res) => { + const userId = req.user.id; // from your auth middleware + + const client = new Apideck({ + apiKey: process.env["APIDECK_API_KEY"] ?? "", + appId: process.env["APIDECK_APP_ID"] ?? "", + consumerId: userId, + }); + + const { data } = await client.vault.sessions.create({ + session: { + consumer_metadata: { + account_name: req.user.company_name, + user_name: req.user.name, + email: req.user.email, + }, + redirect_uri: `${process.env["APP_URL"]}/integrations`, + settings: { + unified_apis: ["accounting", "crm", "hris"], + }, + theme: { + vault_name: "My App", + primary_color: "#4F46E5", + }, + }, + }); + + res.json({ token: data.session_token }); +}); +``` + +### Client-side (Vanilla JS) + +```typescript +import { ApideckVault } from "@apideck/vault-js"; + +async function openVault() { + // Fetch session token from your backend + const response = await fetch("/api/vault/session", { method: "POST" }); + const { token } = await response.json(); + + ApideckVault.open({ + token, + onReady: () => console.log("Vault opened"), + onClose: () => console.log("Vault closed"), + onConnectionChange: (connection) => { + console.log(`${connection.service_id} is now ${connection.state}`); + // Refresh your integrations list + loadIntegrations(); + }, + onConnectionDelete: (connection) => { + console.log(`${connection.service_id} disconnected`); + loadIntegrations(); + }, + }); +} + +document.getElementById("manage-integrations") + ?.addEventListener("click", openVault); +``` + +### Client-side (React) + +```tsx +import { ApideckVault } from "@apideck/vault-js"; +import { useCallback, useState } from "react"; + +function IntegrationsButton() { + const [isLoading, setIsLoading] = useState(false); + + const handleOpenVault = useCallback(async () => { + setIsLoading(true); + try { + const response = await fetch("/api/vault/session", { method: "POST" }); + const { token } = await response.json(); + + ApideckVault.open({ + token, + onReady: () => setIsLoading(false), + onClose: () => console.log("Vault closed"), + onConnectionChange: (connection) => { + console.log("Connection changed:", connection); + }, + }); + } catch (error) { + console.error("Failed to create Vault session:", error); + setIsLoading(false); + } + }, []); + + return ( + + ); +} +``` + +## Filtering by API or Connector + +```typescript +// Show only accounting integrations +ApideckVault.open({ + token: "SESSION_TOKEN", + unifiedApi: "accounting", +}); + +// Open directly to QuickBooks settings +ApideckVault.open({ + token: "SESSION_TOKEN", + serviceId: "quickbooks", + initialView: "settings", +}); + +// Open custom field mapping for Salesforce +ApideckVault.open({ + token: "SESSION_TOKEN", + serviceId: "salesforce", + initialView: "custom-mapping", +}); +``` + +## Theming + +Theme customization is set during session creation on the server side: + +```typescript +const { data } = await apideck.vault.sessions.create({ + session: { + theme: { + vault_name: "My App Integrations", + primary_color: "#4F46E5", + sidepanel_background_color: "#F9FAFB", + sidepanel_text_color: "#111827", + favicon: "https://myapp.com/favicon.ico", + logo: "https://myapp.com/logo.png", + }, + }, +}); +``` + +| Theme Property | Description | +|----------------|-------------| +| `vault_name` | Display name shown in the modal header | +| `primary_color` | Primary accent color (hex) | +| `sidepanel_background_color` | Side panel background (hex) | +| `sidepanel_text_color` | Side panel text color (hex) | +| `favicon` | URL to your favicon | +| `logo` | URL to your logo image | diff --git a/providers/cursor/plugin/skills/apideck-node/references/webhook-api.md b/providers/cursor/plugin/skills/apideck-node/references/webhook-api.md new file mode 100644 index 0000000..457e579 --- /dev/null +++ b/providers/cursor/plugin/skills/apideck-node/references/webhook-api.md @@ -0,0 +1,162 @@ +# Webhook API Reference + +Base namespace: `apideck.webhook` + +Apideck supports both **native webhooks** (from connectors that support them natively) and **virtual webhooks** (Apideck polls the connector and delivers events to your webhook URL). + +## Webhook Subscriptions + +```typescript +// List webhooks +const { data } = await apideck.webhook.webhooks.list(); + +// Create webhook subscription +const { data } = await apideck.webhook.webhooks.create({ + webhook: { + description: "CRM contact changes", + unified_api: "crm", + status: "enabled", + delivery_url: "https://myapp.com/webhooks/apideck", + events: [ + "crm.contact.created", + "crm.contact.updated", + "crm.contact.deleted", + ], + }, +}); + +// Get webhook +const { data } = await apideck.webhook.webhooks.get({ + id: "webhook_123", +}); + +// Update webhook +const { data } = await apideck.webhook.webhooks.update({ + id: "webhook_123", + webhook: { + description: "All CRM changes", + events: [ + "crm.contact.created", + "crm.contact.updated", + "crm.contact.deleted", + "crm.company.created", + "crm.company.updated", + "crm.lead.created", + ], + }, +}); + +// Delete webhook +await apideck.webhook.webhooks.delete({ + id: "webhook_123", +}); +``` + +## Webhook Event Types + +Events follow the pattern `{unified_api}.{resource}.{action}`: + +### Accounting +- `accounting.invoice.created` / `.updated` / `.deleted` +- `accounting.bill.created` / `.updated` / `.deleted` +- `accounting.payment.created` / `.updated` / `.deleted` +- `accounting.customer.created` / `.updated` / `.deleted` +- `accounting.supplier.created` / `.updated` / `.deleted` +- `accounting.ledger-account.created` / `.updated` / `.deleted` +- `accounting.journal-entry.created` / `.updated` / `.deleted` +- `accounting.credit-note.created` / `.updated` / `.deleted` +- `accounting.expense.created` / `.updated` / `.deleted` + +### CRM +- `crm.contact.created` / `.updated` / `.deleted` +- `crm.company.created` / `.updated` / `.deleted` +- `crm.lead.created` / `.updated` / `.deleted` +- `crm.opportunity.created` / `.updated` / `.deleted` +- `crm.activity.created` / `.updated` / `.deleted` +- `crm.note.created` / `.updated` / `.deleted` + +### HRIS +- `hris.employee.created` / `.updated` / `.deleted` / `.terminated` +- `hris.department.created` / `.updated` / `.deleted` +- `hris.company.created` / `.updated` / `.deleted` + +### File Storage +- `file-storage.file.created` / `.updated` / `.deleted` +- `file-storage.folder.created` / `.updated` / `.deleted` + +### ATS +- `ats.applicant.created` / `.updated` / `.deleted` +- `ats.application.created` / `.updated` / `.deleted` +- `ats.job.created` / `.updated` / `.deleted` + +## Webhook Payload Format + +```json +{ + "payload": { + "event_type": "crm.contact.updated", + "unified_api": "crm", + "service_id": "salesforce", + "consumer_id": "user_abc123", + "entity_id": "contact_123", + "entity_type": "contact", + "entity_url": "https://unify.apideck.com/crm/contacts/contact_123", + "occurred_at": "2024-06-15T10:30:00.000Z" + } +} +``` + +## Webhook Signature Verification + +Verify webhook signatures to ensure requests are from Apideck: + +```typescript +import crypto from "crypto"; + +function verifyWebhookSignature( + payload: string, + signature: string, + secret: string +): boolean { + const hmac = crypto.createHmac("sha256", secret); + hmac.update(payload); + const expectedSignature = hmac.digest("base64"); + return crypto.timingSafeEqual( + Buffer.from(signature), + Buffer.from(expectedSignature) + ); +} + +// In your webhook handler +app.post("/webhooks/apideck", (req, res) => { + const signature = req.headers["x-apideck-signature"] as string; + const isValid = verifyWebhookSignature( + JSON.stringify(req.body), + signature, + process.env.APIDECK_WEBHOOK_SECRET! + ); + if (!isValid) { + return res.status(401).send("Invalid signature"); + } + // Process event + const { event_type, entity_id, service_id } = req.body.payload; + console.log(`Received ${event_type} for ${entity_id} from ${service_id}`); + res.status(200).send("OK"); +}); +``` + +## Event Logs + +```typescript +// List webhook event logs +const { data } = await apideck.webhook.eventLogs.list({ + filter: { + exclude_apis: "vault,proxy", + service: { id: "salesforce" }, + consumer_id: "user_abc123", + entity_type: "contact", + event_type: "crm.contact.updated", + }, +}); +// Returns: event delivery status, attempts, request/response details +``` diff --git a/providers/cursor/plugin/skills/apideck-php/SKILL.md b/providers/cursor/plugin/skills/apideck-php/SKILL.md new file mode 100644 index 0000000..d332489 --- /dev/null +++ b/providers/cursor/plugin/skills/apideck-php/SKILL.md @@ -0,0 +1,213 @@ +--- +name: apideck-php +description: Apideck Unified API integration patterns for PHP. 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 using PHP. Covers the apideck-libraries/sdk-php Composer package, authentication, CRUD operations, pagination, error handling, and Vault connection management. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +# Apideck PHP SDK Skill + +## Overview + +The [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 PHP SDK provides typed clients for all unified APIs. + +## Installation + +```sh +composer require apideck-libraries/sdk-php +``` + +## IMPORTANT RULES + +- ALWAYS use the `apideck-libraries/sdk-php` Composer package. DO NOT make raw `Guzzle`/`curl` calls to the Apideck API. +- ALWAYS pass security, app ID, and consumer ID via the builder when creating the client. +- USE `serviceId` on requests to specify which downstream connector to use. +- ALWAYS handle errors with try/catch using `Errors\APIException` as the base class. +- DO NOT store API keys in source code. Use environment variables. + +## Quick Start + +```php +setConsumerId('your-consumer-id') + ->setAppId('your-app-id') + ->setSecurity(getenv('APIDECK_API_KEY')) + ->build(); + +$request = new Operations\CrmContactsAllRequest( + serviceId: 'salesforce', + limit: 20, +); + +$responses = $sdk->crm->contacts->list(request: $request); + +foreach ($responses as $response) { + if ($response->httpMeta->response->getStatusCode() === 200) { + foreach ($response->getContactsResponse->data as $contact) { + echo $contact->name . "\n"; + } + } +} +``` + +## SDK Patterns + +### Client Setup + +```php +use Apideck\Unify; + +$sdk = Unify\Apideck::builder() + ->setConsumerId('your-consumer-id') + ->setAppId('your-app-id') + ->setSecurity(getenv('APIDECK_API_KEY')) + ->build(); +``` + +### CRUD Operations + +All resources follow the pattern: `$sdk->{api}->{resource}->{operation}(request: $request)`. + +```php +use Apideck\Unify\Models\Operations; +use Apideck\Unify\Models\Components; + +// LIST +$request = new Operations\CrmContactsAllRequest( + serviceId: 'salesforce', + limit: 20, + filter: new Components\ContactsFilter(email: 'john@example.com'), + sort: new Components\ContactsSort( + by: Components\ContactsSortBy::UpdatedAt, + direction: Components\SortDirection::Desc, + ), +); +$responses = $sdk->crm->contacts->list(request: $request); + +// CREATE +$request = new Operations\CrmContactsAddRequest( + serviceId: 'salesforce', + contact: new Components\ContactInput( + firstName: 'John', + lastName: 'Doe', + emails: [ + new Components\Email(email: 'john@example.com', type: Components\EmailType::Primary), + ], + phoneNumbers: [ + new Components\PhoneNumber(number: '+1234567890', type: Components\PhoneNumberType::Mobile), + ], + ), +); +$response = $sdk->crm->contacts->create(request: $request); + +// GET +$request = new Operations\CrmContactsOneRequest( + id: 'contact_123', + serviceId: 'salesforce', +); +$response = $sdk->crm->contacts->get(request: $request); + +// UPDATE +$request = new Operations\CrmContactsUpdateRequest( + id: 'contact_123', + serviceId: 'salesforce', + contact: new Components\ContactInput(firstName: 'Jane'), +); +$response = $sdk->crm->contacts->update(request: $request); + +// DELETE +$request = new Operations\CrmContactsDeleteRequest( + id: 'contact_123', + serviceId: 'salesforce', +); +$response = $sdk->crm->contacts->delete(request: $request); +``` + +### Pagination + +The `list` method returns a PHP Generator. Iterate with `foreach`: + +```php +$request = new Operations\AccountingInvoicesAllRequest( + serviceId: 'quickbooks', + limit: 50, +); + +$responses = $sdk->accounting->invoices->list(request: $request); + +foreach ($responses as $response) { + if ($response->httpMeta->response->getStatusCode() === 200) { + foreach ($response->getInvoicesResponse->data as $invoice) { + echo "{$invoice->number}: {$invoice->total}\n"; + } + } +} +``` + +### Error Handling + +```php +use Apideck\Unify\Models\Errors; + +try { + $responses = $sdk->crm->contacts->list(request: $request); + foreach ($responses as $response) { + // handle response + } +} catch (Errors\BadRequestResponseThrowable $e) { + echo "Bad request: " . $e->getMessage() . "\n"; +} catch (Errors\UnauthorizedResponseThrowable $e) { + echo "Unauthorized\n"; +} catch (Errors\NotFoundResponseThrowable $e) { + echo "Not found\n"; +} catch (Errors\PaymentRequiredResponseThrowable $e) { + echo "API limit reached\n"; +} catch (Errors\UnprocessableResponseThrowable $e) { + echo "Validation error: " . $e->getMessage() . "\n"; +} catch (Errors\APIException $e) { + echo "API error: " . $e->getMessage() . "\n"; +} +``` + +### Retry Configuration + +```php +use Apideck\Unify\Utils\Retry; + +// Global +$sdk = Unify\Apideck::builder() + ->setRetryConfig( + new Retry\RetryConfigBackoff( + initialInterval: 1, + maxInterval: 50, + exponent: 1.1, + maxElapsedTime: 100, + retryConnectionErrors: false, + ) + ) + ->setConsumerId('your-consumer-id') + ->setAppId('your-app-id') + ->setSecurity(getenv('APIDECK_API_KEY')) + ->build(); +``` + +## API Namespaces + +| Namespace | Resources | +|-----------|-----------| +| `$sdk->accounting->*` | invoices, bills, payments, customers, suppliers, ledgerAccounts, journalEntries, taxRates, creditNotes, purchaseOrders, balanceSheet, profitAndLoss, and more | +| `$sdk->crm->*` | contacts, companies, leads, opportunities, activities, notes, pipelines, users | +| `$sdk->hris->*` | employees, companies, departments, payrolls, timeOffRequests | +| `$sdk->fileStorage->*` | files, folders, drives, driveGroups, sharedLinks, uploadSessions | +| `$sdk->ats->*` | applicants, applications, jobs | +| `$sdk->vault->*` | connections, consumers, sessions, customMappings, logs | +| `$sdk->webhook->*` | webhooks, eventLogs | diff --git a/providers/cursor/plugin/skills/apideck-portman/SKILL.md b/providers/cursor/plugin/skills/apideck-portman/SKILL.md new file mode 100644 index 0000000..a549727 --- /dev/null +++ b/providers/cursor/plugin/skills/apideck-portman/SKILL.md @@ -0,0 +1,384 @@ +--- +name: apideck-portman +description: API contract testing with Portman by Apideck. Use when generating Postman collections from OpenAPI specs, writing contract tests, variation tests, integration tests, fuzz testing, or setting up CI/CD API test pipelines. Portman converts OpenAPI 3.x specs into Postman collections with auto-generated test suites. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +# Portman API Testing Skill + +## Overview + +[Portman](https://github.com/apideck-libraries/portman) converts OpenAPI 3.x specifications into Postman collections with auto-generated contract tests, variation tests, content tests, and integration tests. It runs tests via Newman (Postman's CLI runner) and integrates into CI/CD pipelines. + +## Installation + +```sh +npm install -g @apideck/portman +``` + +Or use without installing: + +```sh +npx @apideck/portman -l your-openapi-spec.yaml +``` + +## IMPORTANT RULES + +- ALWAYS use a `portman-config.json` (or `.yaml`) for test configuration. Do not rely solely on defaults for production use. +- ALWAYS target operations using `openApiOperationId` or `openApiOperation` (method::path) syntax. +- USE `--baseUrl` to override the spec's server URL when testing against local/staging environments. +- USE `--envFile` to inject environment variables. Variables prefixed with `PORTMAN_` auto-map to Postman collection variables. +- USE `assignVariables` to chain request/response values across operations (e.g., capture `id` from create, use in get/update/delete). +- DO NOT hardcode secrets in portman-config. Use environment variables and `.env` files. + +## Quick Start + +```sh +# Generate collection from local spec +portman -l ./openapi.yaml + +# Generate and run tests against live API +portman -l ./openapi.yaml -b https://api.example.com -n true + +# With custom config +portman -l ./openapi.yaml -c ./portman-config.json -b https://api.example.com -n true +``` + +## Configuration File + +Create `portman-config.json` (or `.yaml`): + +```json +{ + "version": 1.0, + "tests": { + "contractTests": [], + "contentTests": [], + "variationTests": [], + "integrationTests": [], + "extendTests": [] + }, + "assignVariables": [], + "overwrites": [], + "globals": {} +} +``` + +JSON Schema: `https://raw.githubusercontent.com/apideck-libraries/portman/main/src/utils/portman-config-schema.json` + +## Targeting Operations + +All test and overwrite sections use the same targeting system: + +```json +// By operationId +{ "openApiOperationId": "leadsAdd" } + +// By multiple operationIds +{ "openApiOperationIds": ["leadsAdd", "leadsAll"] } + +// By method::path (supports wildcards) +{ "openApiOperation": "GET::/crm/leads" } +{ "openApiOperation": "*::/crm/*" } +{ "openApiOperation": "POST::/*" } + +// Exclude specific operations +{ "openApiOperation": "*::/crm/*", "excludeForOperations": ["leadsDelete"] } +``` + +## Contract Tests + +Validate API responses conform to the OpenAPI spec: + +```json +{ + "tests": { + "contractTests": [ + { + "openApiOperation": "*::/*", + "statusSuccess": { "enabled": true }, + "contentType": { "enabled": true }, + "jsonBody": { "enabled": true }, + "schemaValidation": { "enabled": true }, + "headersPresent": { "enabled": true } + }, + { + "openApiOperation": "*::/*", + "responseTime": { "enabled": true, "maxMs": 300 } + } + ] + } +} +``` + +| Test | Description | +|------|-------------| +| `statusSuccess` | Response returns 2xx | +| `statusCode` | Response returns specific HTTP code | +| `contentType` | Content-Type matches spec | +| `jsonBody` | Body is valid JSON matching spec | +| `schemaValidation` | Body validates against JSON schema | +| `headersPresent` | Required headers are present | +| `responseTime` | Response within `maxMs` milliseconds | + +## Content Tests + +Validate specific response values: + +```json +{ + "tests": { + "contentTests": [ + { + "openApiOperationId": "leadsAll", + "responseBodyTests": [ + { "key": "status_code", "value": 200 }, + { "key": "data[0].id", "assert": "not.to.be.null" }, + { "key": "data", "minLength": 1 }, + { "key": "resource", "oneOf": ["leads", "contacts"] } + ], + "responseHeaderTests": [ + { "key": "content-type", "contains": "application/json" } + ] + } + ] + } +} +``` + +Content test assertions: `value` (exact), `contains` (substring), `oneOf`, `length`, `minLength`, `maxLength`, `notExist`, `assert` (Postman assertion string). + +## Variation Tests + +Test alternative scenarios (errors, edge cases, unauthorized access): + +```json +{ + "tests": { + "variationTests": [ + { + "openApiOperation": "*::/crm/*", + "openApiResponse": "401", + "variations": [ + { + "name": "Unauthorized", + "overwrites": [ + { "overwriteRequestSecurity": { "bearer": { "token": "invalid" } } } + ], + "tests": { + "contractTests": [{ "statusCode": { "enabled": true } }] + } + } + ] + }, + { + "openApiOperationId": "leadsAdd", + "openApiResponse": "400", + "variations": [ + { + "name": "MissingRequiredFields", + "overwrites": [ + { "overwriteRequestBody": [{ "key": "name", "value": "", "overwrite": true }] } + ], + "tests": { + "contractTests": [ + { "statusCode": { "enabled": true } }, + { "schemaValidation": { "enabled": true } } + ] + } + } + ] + } + ] + } +} +``` + +## Fuzz Testing + +Auto-generate invalid values based on schema constraints: + +```json +{ + "tests": { + "variationTests": [ + { + "openApiOperation": "*::/crm/*", + "openApiResponse": "422", + "variations": [ + { + "name": "FuzzTest", + "fuzzing": [ + { + "requestBody": [ + { + "requiredFields": { "enabled": true }, + "minimumNumberFields": { "enabled": true }, + "maximumNumberFields": { "enabled": true }, + "minLengthFields": { "enabled": true }, + "maxLengthFields": { "enabled": true } + } + ] + } + ], + "tests": { + "contractTests": [{ "statusCode": { "enabled": true } }] + } + } + ] + } + ] + } +} +``` + +Fuzzing targets: `requestBody`, `requestQueryParams`, `requestHeaders`. + +## Integration Tests + +Group operations into end-to-end workflows: + +```json +{ + "tests": { + "integrationTests": [ + { + "name": "Lead Lifecycle", + "operations": [ + { "openApiOperationId": "leadsAdd" }, + { "openApiOperationId": "leadsOne" }, + { "openApiOperationId": "leadsUpdate" }, + { "openApiOperationId": "leadsDelete" } + ] + } + ] + } +} +``` + +## Variable Chaining + +Capture values from responses to use in subsequent requests: + +```json +{ + "assignVariables": [ + { + "openApiOperationId": "leadsAdd", + "collectionVariables": [ + { "responseBodyProp": "data.id", "name": "leadId" }, + { "responseHeaderProp": "x-request-id", "name": "requestId" } + ] + } + ] +} +``` + +Use captured variables in overwrites: `{{leadId}}`, `{{requestId}}`. + +## Request Overwrites + +Modify generated requests: + +```json +{ + "overwrites": [ + { + "openApiOperationId": "leadsAdd", + "overwriteRequestBody": [ + { "key": "name", "value": "Test Lead {{$randomInt}}", "overwrite": true } + ], + "overwriteRequestHeaders": [ + { "key": "x-apideck-consumer-id", "value": "{{consumerId}}", "overwrite": true } + ] + }, + { + "openApiOperation": "DELETE::/crm/leads/{id}", + "overwriteRequestPathVariables": [ + { "key": "id", "value": "{{leadId}}", "overwrite": true } + ] + } + ] +} +``` + +Security overwrites: `overwriteRequestSecurity` supports `bearer`, `apiKey`, `basic`, `oauth2`, and `remove`. + +## Globals + +```json +{ + "globals": { + "collectionPreRequestScripts": ["pm.collectionVariables.set('timestamp', Date.now());"], + "securityOverwrites": { + "bearer": { "token": "{{bearerToken}}" } + }, + "keyValueReplacements": { "x-apideck-app-id": "{{applicationId}}" }, + "valueReplacements": { "": "{{bearerToken}}" }, + "orderOfOperations": ["leadsAdd", "leadsAll", "leadsOne", "leadsUpdate", "leadsDelete"], + "stripResponseExamples": true, + "variableCasing": "camelCase" + } +} +``` + +## Environment Variables + +Variables prefixed with `PORTMAN_` in `.env` are auto-injected as camelCase Postman variables: + +``` +PORTMAN_CONSUMER_ID=test_user → {{consumerId}} +PORTMAN_API_TOKEN=abc123 → {{apiToken}} +``` + +## CI/CD Integration + +Store all options in a CLI options file: + +```json +{ + "local": "./specs/crm.yml", + "baseUrl": "https://staging-api.example.com", + "output": "./output/crm.postman.json", + "portmanConfigFile": "./config/portman-config.json", + "envFile": "./.env", + "includeTests": true, + "runNewman": true +} +``` + +```sh +portman --cliOptionsFile ./portman-cli-options.json +``` + +## Testing Apideck APIs + +```sh +# Test CRM API +portman -u https://specs.apideck.com/crm.yml -c ./portman-config.json -b https://unify.apideck.com -n true + +# Test Accounting API +portman -u https://specs.apideck.com/accounting.yml -c ./portman-config.json -b https://unify.apideck.com -n true +``` + +## CLI Reference + +| Flag | Description | +|------|-------------| +| `-l, --local` | Path to local OpenAPI spec | +| `-u, --url` | URL of remote OpenAPI spec | +| `-b, --baseUrl` | Override base URL | +| `-o, --output` | Output file path | +| `-c, --portmanConfigFile` | Path to portman-config | +| `-n, --runNewman` | Run Newman after generation | +| `-t, --includeTests` | Include test suite (default: true) | +| `-d, --newmanIterationData` | Path to iteration data | +| `--envFile` | Path to .env file | +| `--syncPostman` | Upload to Postman app | +| `--bundleContractTests` | Separate folder for contract tests | +| `--cliOptionsFile` | Path to CLI options file | +| `--init` | Interactive config wizard | diff --git a/providers/cursor/plugin/skills/apideck-python/SKILL.md b/providers/cursor/plugin/skills/apideck-python/SKILL.md new file mode 100644 index 0000000..85c3496 --- /dev/null +++ b/providers/cursor/plugin/skills/apideck-python/SKILL.md @@ -0,0 +1,245 @@ +--- +name: apideck-python +description: Apideck Unified API integration patterns for Python. 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 using Python. Covers the apideck-unify SDK, authentication, CRUD operations, pagination, filtering, async support, and Vault connection management. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +# Apideck Python SDK Skill + +## Overview + +The [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 Python SDK (`apideck-unify`) provides typed clients for all unified APIs. + +## Installation + +```sh +pip install apideck-unify +``` + +Requires Python 3.9+. Dependencies: `httpx`, `pydantic`. + +## IMPORTANT RULES + +- ALWAYS use the `apideck-unify` SDK. DO NOT make raw `httpx`/`requests` calls to the Apideck API. +- ALWAYS pass `api_key`, `app_id`, and `consumer_id` when initializing the client. +- ALWAYS set the `APIDECK_API_KEY` environment variable rather than hardcoding API keys. +- USE `service_id` to specify which downstream connector to use (e.g., `"salesforce"`, `"quickbooks"`). If a consumer has multiple connections for an API, `service_id` is required. +- USE context managers (`with` / `async with`) for client lifecycle management. +- USE the `fields` parameter to request only the columns you need. +- USE the `filter_` parameter (note the trailing underscore) to narrow results server-side. +- ALWAYS handle errors with try/except using `models.ApideckError` as the base class. + +## Quick Start + +```python +from apideck_unify import Apideck +import os + +with Apideck( + api_key=os.getenv("APIDECK_API_KEY", ""), + app_id="your-app-id", + consumer_id="your-consumer-id", +) as apideck: + res = apideck.crm.contacts.list( + service_id="salesforce", + limit=20, + filter_={"email": "john@example.com"}, + ) + while res is not None: + for contact in res.data: + print(contact.name, contact.emails) + res = res.next() +``` + +## SDK Patterns + +### Client Setup + +```python +from apideck_unify import Apideck +import os + +with Apideck( + api_key=os.getenv("APIDECK_API_KEY", ""), + app_id="your-app-id", + consumer_id="your-consumer-id", +) as apideck: + # Make API calls here + pass +``` + +The `consumer_id` 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}()`. + +```python +import apideck_unify +from apideck_unify import Apideck +import os + +with Apideck( + api_key=os.getenv("APIDECK_API_KEY", ""), + app_id="your-app-id", + consumer_id="your-consumer-id", +) as apideck: + + # LIST - retrieve multiple records + res = apideck.crm.contacts.list( + service_id="salesforce", + limit=20, + filter_={"email": "john@example.com", "company_id": "12345"}, + sort={"by": apideck_unify.ContactsSortBy.CREATED_AT, "direction": apideck_unify.SortDirection.DESC}, + fields="id,name,email", + ) + + # CREATE - create a new record + res = apideck.crm.contacts.create( + service_id="salesforce", + first_name="John", + last_name="Doe", + emails=[{"email": "john@example.com", "type": apideck_unify.EmailType.PRIMARY}], + phone_numbers=[{"number": "+1234567890", "type": apideck_unify.PhoneNumberType.PRIMARY}], + ) + print(res.create_contact_response) + + # GET - retrieve a single record + res = apideck.crm.contacts.get(id="contact_123", service_id="salesforce") + + # UPDATE - modify an existing record + res = apideck.crm.contacts.update(id="contact_123", service_id="salesforce", first_name="Jane") + + # DELETE - remove a record + res = apideck.crm.contacts.delete(id="contact_123", service_id="salesforce") +``` + +### Pagination + +Use the `.next()` method on response objects for cursor-based pagination: + +```python +res = apideck.accounting.invoices.list(service_id="quickbooks", limit=50) + +while res is not None: + for invoice in res.data: + print(invoice.number, invoice.total) + res = res.next() +``` + +### Async Support + +Every sync method has an `_async` counterpart. Use `async with` as context manager: + +```python +import asyncio +from apideck_unify import Apideck +import os + +async def main(): + async with Apideck( + api_key=os.getenv("APIDECK_API_KEY", ""), + app_id="your-app-id", + consumer_id="your-consumer-id", + ) as apideck: + res = await apideck.crm.contacts.list_async( + service_id="salesforce", + limit=20, + ) + while res is not None: + for contact in res.data: + print(contact.name) + res = res.next() + +asyncio.run(main()) +``` + +### Error Handling + +```python +from apideck_unify import Apideck, models + +try: + res = apideck.crm.contacts.get(id="invalid", service_id="salesforce") +except models.BadRequestResponse as e: + print("Bad request:", e.message, e.status_code) +except models.UnauthorizedResponse as e: + print("Invalid API key or missing credentials") +except models.NotFoundResponse as e: + print("Record not found") +except models.PaymentRequiredResponse as e: + print("API limit reached") +except models.UnprocessableResponse as e: + print("Validation error:", e.message) +except models.ApideckError as e: + print(f"API error {e.status_code}: {e.message}") +``` + +All exceptions inherit from `models.ApideckError` with properties: `message`, `status_code`, `headers`, `body`, `raw_response`. + +### Common Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `service_id` | `str` | Downstream connector ID (e.g., `"quickbooks"`, `"salesforce"`) | +| `limit` | `int` | Max results per page (1-200, default 20) | +| `cursor` | `str` | Pagination cursor from previous response | +| `filter_` | `dict` | Resource-specific filter criteria (note trailing underscore) | +| `sort` | `dict` | `{"by": SortField, "direction": SortDirection}` | +| `fields` | `str` | Comma-separated field names to return | +| `pass_through` | `dict` | Pass-through query parameters for the downstream API | +| `raw` | `bool` | Include raw downstream response when `True` | +| `retry_config` | `RetryConfig` | Per-call retry override | + +### Pass-Through Parameters + +```python +# Query pass-through +res = apideck.accounting.invoices.list( + service_id="quickbooks", + pass_through={"search": "overdue"}, +) + +# Body pass-through for connector-specific fields +res = apideck.crm.contacts.create( + service_id="salesforce", + first_name="John", + last_name="Doe", + pass_through=[{ + "service_id": "salesforce", + "operation_id": "contactsAdd", + "extend_object": {"custom_sf_field__c": "value"}, + }], +) +``` + +### Retry Configuration + +```python +from apideck_unify import Apideck +from apideck_unify.utils import BackoffStrategy, RetryConfig + +with Apideck( + api_key=os.getenv("APIDECK_API_KEY", ""), + app_id="your-app-id", + consumer_id="your-consumer-id", + retry_config=RetryConfig("backoff", BackoffStrategy(1, 50, 1.1, 100), False), +) as apideck: + pass +``` + +## API Namespaces + +| Namespace | Resources | +|-----------|-----------| +| `apideck.accounting.*` | invoices, bills, payments, customers, suppliers, ledger_accounts, journal_entries, tax_rates, credit_notes, purchase_orders, balance_sheet, profit_and_loss, expenses, attachments, and more | +| `apideck.crm.*` | contacts, companies, leads, opportunities, activities, notes, pipelines, users | +| `apideck.hris.*` | employees, companies, departments, payrolls, time_off_requests | +| `apideck.file_storage.*` | files, folders, drives, shared_links, upload_sessions | +| `apideck.ats.*` | applicants, applications, jobs | +| `apideck.vault.*` | connections, consumers, sessions, custom_mappings, logs | +| `apideck.webhook.*` | webhooks, event_logs | diff --git a/providers/cursor/plugin/skills/apideck-rest/SKILL.md b/providers/cursor/plugin/skills/apideck-rest/SKILL.md new file mode 100644 index 0000000..3b54bc8 --- /dev/null +++ b/providers/cursor/plugin/skills/apideck-rest/SKILL.md @@ -0,0 +1,301 @@ +--- +name: apideck-rest +description: Apideck Unified REST API reference for any language. 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 using direct HTTP calls. Covers authentication headers, CRUD operations, cursor-based pagination, filtering, sorting, error handling, rate limiting, pass-through parameters, and webhooks. Language-agnostic — works with curl, fetch, axios, httpx, or any HTTP client. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +# Apideck REST API Skill + +## Overview + +The [Apideck Unified API](https://apideck.com) provides a single REST endpoint to connect with 200+ third-party services across accounting, CRM, HRIS, file storage, ATS, e-commerce, and more. This skill covers direct HTTP usage for any language. + +**Base URL:** `https://unify.apideck.com` + +## IMPORTANT RULES + +- ALWAYS include the three required headers: `Authorization`, `x-apideck-app-id`, and `x-apideck-consumer-id`. +- ALWAYS make API calls server-side to prevent token leakage. +- USE `x-apideck-service-id` to specify which downstream connector to use. Required when a consumer has multiple connections for the same API. +- USE cursor-based pagination — iterate until `meta.cursors.next` is `null`. +- USE the `filter` query parameters to narrow results server-side. DO NOT fetch all records and filter client-side. +- USE the `fields` query parameter to request only the columns you need. +- DO NOT store API keys in source code. Use environment variables. + +## Authentication + +Every request requires these headers: + +| Header | Required | Description | +|--------|----------|-------------| +| `Authorization` | Yes | `Bearer {API_KEY}` | +| `x-apideck-app-id` | Yes | Your Apideck application ID | +| `x-apideck-consumer-id` | Yes | End-user/customer ID stored in Vault | +| `x-apideck-service-id` | No | Downstream connector ID (e.g., `salesforce`, `quickbooks`) | +| `Content-Type` | Yes (POST/PATCH) | `application/json` | + +## CRUD Operations + +All resources follow a consistent URL pattern: + +``` +GET /{api}/{resource} → List +POST /{api}/{resource} → Create +GET /{api}/{resource}/{id} → Get +PATCH /{api}/{resource}/{id} → Update +DELETE /{api}/{resource}/{id} → Delete +``` + +### List + +```bash +curl -X GET 'https://unify.apideck.com/crm/contacts?limit=20&filter[email]=john@example.com&sort[by]=updated_at&sort[direction]=desc&fields=id,name,email' \ + -H 'Authorization: Bearer {API_KEY}' \ + -H 'x-apideck-app-id: {APP_ID}' \ + -H 'x-apideck-consumer-id: {CONSUMER_ID}' \ + -H 'x-apideck-service-id: salesforce' +``` + +Response: +```json +{ + "status_code": 200, + "status": "OK", + "service": "salesforce", + "resource": "contacts", + "operation": "all", + "data": [ + { "id": "contact_123", "name": "John Doe", "email": "john@example.com" } + ], + "meta": { + "items_on_page": 20, + "cursors": { + "previous": null, + "current": "em9oby1jcm06Om9mZnNldDo6MA==", + "next": "em9oby1jcm06Om9mZnNldDo6MjA=" + } + }, + "links": { + "previous": null, + "current": "https://unify.apideck.com/crm/contacts?cursor=...", + "next": "https://unify.apideck.com/crm/contacts?cursor=..." + } +} +``` + +### Create + +```bash +curl -X POST 'https://unify.apideck.com/crm/contacts' \ + -H 'Authorization: Bearer {API_KEY}' \ + -H 'Content-Type: application/json' \ + -H 'x-apideck-app-id: {APP_ID}' \ + -H 'x-apideck-consumer-id: {CONSUMER_ID}' \ + -H 'x-apideck-service-id: salesforce' \ + -d '{ + "first_name": "John", + "last_name": "Doe", + "title": "VP of Engineering", + "emails": [{"email": "john@example.com", "type": "primary"}], + "phone_numbers": [{"number": "+1234567890", "type": "mobile"}], + "addresses": [{ + "type": "primary", + "street_1": "123 Main St", + "city": "San Francisco", + "state": "CA", + "postal_code": "94105", + "country": "US" + }] + }' +``` + +Response: `201 Created` with `{ "data": { "id": "contact_123" } }` + +### Get + +```bash +curl -X GET 'https://unify.apideck.com/crm/contacts/contact_123' \ + -H 'Authorization: Bearer {API_KEY}' \ + -H 'x-apideck-app-id: {APP_ID}' \ + -H 'x-apideck-consumer-id: {CONSUMER_ID}' \ + -H 'x-apideck-service-id: salesforce' +``` + +### Update + +```bash +curl -X PATCH 'https://unify.apideck.com/crm/contacts/contact_123' \ + -H 'Authorization: Bearer {API_KEY}' \ + -H 'Content-Type: application/json' \ + -H 'x-apideck-app-id: {APP_ID}' \ + -H 'x-apideck-consumer-id: {CONSUMER_ID}' \ + -H 'x-apideck-service-id: salesforce' \ + -d '{"title": "CTO"}' +``` + +### Delete + +```bash +curl -X DELETE 'https://unify.apideck.com/crm/contacts/contact_123' \ + -H 'Authorization: Bearer {API_KEY}' \ + -H 'x-apideck-app-id: {APP_ID}' \ + -H 'x-apideck-consumer-id: {CONSUMER_ID}' \ + -H 'x-apideck-service-id: salesforce' +``` + +## Pagination + +Apideck uses cursor-based pagination. Pass the `next` cursor from the response to fetch subsequent pages: + +| Parameter | Type | Default | Range | +|-----------|------|---------|-------| +| `limit` | integer | 20 | 1-200 | +| `cursor` | string | — | Opaque cursor from `meta.cursors.next` | + +```bash +# First page +curl 'https://unify.apideck.com/crm/contacts?limit=50' -H '...' + +# Next page +curl 'https://unify.apideck.com/crm/contacts?limit=50&cursor=em9oby1jcm06Om9mZnNldDo6NTA=' -H '...' +``` + +When `meta.cursors.next` is `null`, you have reached the last page. + +## Filtering and Sorting + +### Filters + +``` +?filter[field_name]=value +``` + +Available filters vary by resource. Common examples: + +| Resource | Filters | +|----------|---------| +| CRM Contacts | `filter[name]`, `filter[email]`, `filter[phone_number]`, `filter[company_id]`, `filter[owner_id]`, `filter[first_name]`, `filter[last_name]` | +| CRM Opportunities | `filter[status]`, `filter[title]`, `filter[company_id]`, `filter[owner_id]` | +| Accounting Invoices | `filter[updated_since]` (ISO 8601 datetime) | +| General | `filter[updated_since]` for incremental sync | + +### Sorting + +``` +?sort[by]=updated_at&sort[direction]=desc +``` + +### Field Selection + +``` +?fields=id,name,email,phone_numbers +``` + +## Pass-Through Parameters + +For connector-specific query parameters not in the unified model: + +``` +?pass_through[search]=overdue +``` + +For connector-specific fields in request bodies: + +```json +{ + "first_name": "John", + "pass_through": [ + { + "service_id": "salesforce", + "operation_id": "contactsAdd", + "extend_object": { + "custom_sf_field__c": "value" + } + } + ] +} +``` + +## Error Handling + +All errors follow this format: + +```json +{ + "status_code": 400, + "error": "Bad Request", + "type_name": "RequestValidationError", + "message": "Human-readable error description", + "detail": "Parameter-specific info", + "ref": "https://developers.apideck.com/errors#requestvalidationerror" +} +``` + +| Code | Meaning | +|------|---------| +| 400 | Bad Request — invalid parameters | +| 401 | Unauthorized — invalid API key | +| 402 | Payment Required — API limit reached | +| 404 | Not Found — resource does not exist | +| 422 | Unprocessable Entity — validation error | +| 429 | Too Many Requests — rate limit exceeded | +| 5xx | Server Error — Apideck or downstream failure | + +## Rate Limiting + +Apideck normalizes downstream rate limit headers: + +| Header | Description | +|--------|-------------| +| `x-downstream-ratelimit-limit` | Total request capacity | +| `x-downstream-ratelimit-remaining` | Remaining requests | +| `x-downstream-ratelimit-reset` | Unix timestamp when limits reset | + +## Raw Mode + +Append `?raw=true` to include the unmodified downstream response in a `_raw` property alongside normalized data. + +## Available API Endpoints + +| API | URL Prefix | Resources | +|-----|-----------|-----------| +| CRM | `/crm/` | contacts, companies, leads, opportunities, activities, notes, pipelines, users | +| Accounting | `/accounting/` | invoices, bills, payments, customers, suppliers, ledger-accounts, journal-entries, tax-rates, credit-notes, purchase-orders, balance-sheet, profit-and-loss | +| HRIS | `/hris/` | employees, companies, departments, payrolls, time-off-requests | +| File Storage | `/file-storage/` | files, folders, drives, drive-groups, shared-links, upload-sessions | +| ATS | `/ats/` | applicants, applications, jobs | +| Vault | `/vault/` | connections, sessions, consumers, custom-mappings, logs | +| Webhook | `/webhook/` | webhooks, event-logs | + +## Webhook Events + +Events follow the pattern `{api}.{resource}.{action}`: + +``` +crm.contact.created / .updated / .deleted +accounting.invoice.created / .updated / .deleted +hris.employee.created / .updated / .deleted / .terminated +file-storage.file.created / .updated / .deleted +ats.applicant.created / .updated / .deleted +``` + +Payload: +```json +{ + "payload": { + "event_type": "crm.contact.updated", + "unified_api": "crm", + "service_id": "salesforce", + "consumer_id": "user_abc123", + "entity_id": "contact_123", + "entity_type": "contact", + "occurred_at": "2024-06-15T10:30:00.000Z" + } +} +``` + +Verify signatures using the `x-apideck-signature` header with HMAC-SHA256. diff --git a/skills/apideck-best-practices/SKILL.md b/skills/apideck-best-practices/SKILL.md new file mode 100644 index 0000000..91fc2b9 --- /dev/null +++ b/skills/apideck-best-practices/SKILL.md @@ -0,0 +1,176 @@ +--- +name: apideck-best-practices +description: Best practices for building Apideck integrations. Covers authentication patterns, pagination, error handling, connection management with Vault, webhook setup, and common pitfalls. Use when designing or reviewing any Apideck integration regardless of language. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +The Apideck Unified API base URL is `https://unify.apideck.com`. All API calls must be made server-side to prevent token leakage. + +## Authentication + +Every API call requires three headers: `Authorization: Bearer {API_KEY}`, `x-apideck-app-id`, and `x-apideck-consumer-id`. The `x-apideck-service-id` header specifies which downstream connector to use (e.g., `salesforce`, `quickbooks`, `xero`). When a consumer has multiple connections for the same unified API, `x-apideck-service-id` is required. + +Never hardcode API keys in source code. Always use environment variables or a secrets manager. Never expose API keys to the client/browser. + +## SDK Selection + +Always use the official Apideck SDK for the user's language. Do not make raw HTTP calls when an SDK is available: + +| Language | Package | +|----------|---------| +| TypeScript/Node.js | `@apideck/unify` | +| Python | `apideck-unify` | +| C# / .NET | `ApideckUnifySdk` | +| Java | `com.apideck:unify` | +| Go | `github.com/apideck-libraries/sdk-go` | +| PHP | `apideck-libraries/sdk-php` | + +All SDKs follow the same CRUD pattern: `client.{api}.{resource}.{operation}()`. All SDKs support retry configuration with exponential backoff. + +## Consumer ID Architecture + +The `consumerId` represents your end-user — the person whose third-party connections you're accessing. In multi-tenant SaaS applications: + +- Create one Apideck consumer per customer/tenant +- Store the mapping between your user IDs and Apideck consumer IDs +- Pass the correct `consumerId` per request — never use a shared consumer for all users +- Use [Vault sessions](https://developers.apideck.com/guides/vault) to let each user manage their own connections + +## Connection Management + +Always use [Apideck Vault](https://developers.apideck.com/guides/vault) for managing end-user connections. Never build custom OAuth flows when Vault handles them. + +Use [`@apideck/vault-js`](https://www.npmjs.com/package/@apideck/vault-js) to embed the connection management modal in your frontend. Session creation must always happen server-side: + +1. Server-side: Create a Vault session via `vault.sessions.create()` with the consumer's metadata +2. Client-side: Open the modal with `ApideckVault.open({ token })` +3. Handle `onConnectionChange` callbacks to update your UI when users authorize/modify connections + +Customize the Vault modal appearance via session `theme` properties (logo, colors, vault name) to match your brand. + +## Pagination + +Apideck uses cursor-based pagination across all list endpoints. Always paginate — never assume a single page returns all records. + +- Set `limit` (1-200, default 20) to control page size +- Use the SDK's built-in pagination: `for await...of` (Node.js), `.next()` (Python/Go/.NET), `callAsStream()` (Java), `foreach` generator (PHP) +- Stop when the next cursor is `null` + +For incremental sync, use `filter[updated_since]` with an ISO 8601 timestamp to fetch only records modified since your last sync. + +## Filtering and Field Selection + +Always filter server-side using the `filter` parameter. Never fetch all records and filter client-side — this wastes API units and increases response time. + +Always use the `fields` parameter to request only the columns you need. This reduces response size and improves performance. Example: `fields=id,name,email,updated_at`. + +## Error Handling + +Always handle errors. All SDKs provide typed error classes: + +| HTTP Code | Meaning | Action | +|-----------|---------|--------| +| 400 | Bad Request | Fix request parameters | +| 401 | Unauthorized | Check API key and consumer credentials | +| 402 | Payment Required | API limit reached — upgrade plan or wait | +| 404 | Not Found | Resource does not exist or wrong service ID | +| 422 | Unprocessable | Validation error — check required fields | +| 429 | Rate Limited | Back off and retry (check `x-downstream-ratelimit-reset` header) | +| 5xx | Server Error | Retry with exponential backoff | + +For downstream connector errors, inspect the `detail` and `downstream_errors` fields to get the original error from the third-party service. + +## Pass-Through for Connector-Specific Fields + +When the unified model doesn't cover a connector-specific field, use `pass_through` in the request body: + +```json +{ + "first_name": "John", + "pass_through": [ + { + "service_id": "salesforce", + "operation_id": "contactsAdd", + "extend_object": { "custom_sf_field__c": "value" } + } + ] +} +``` + +Use [custom field mapping](https://developers.apideck.com/guides/custom-mapping) in Vault to let end-users map their connector-specific fields without code changes. + +## Webhooks + +Use Apideck webhooks for real-time notifications instead of polling. Apideck supports both native webhooks (from connectors that support them) and virtual webhooks (polling-based for connectors that don't). + +Always verify webhook signatures using the `x-apideck-signature` header with HMAC-SHA256. Never process unverified webhook payloads. + +Webhook events follow the pattern `{api}.{resource}.{action}` (e.g., `crm.contact.created`, `accounting.invoice.updated`). + +## Logs + +Apideck provides detailed API call logs for every request made through the platform. Logs are available both in the Apideck dashboard and via the API. Use logs to debug failed requests, inspect downstream responses, and monitor integration health. + +Access logs programmatically via the Vault API: `vault.logs.list()`. Each log entry includes the HTTP method, URL, status code, request/response bodies, downstream service, and timestamps. + +Use logs when: + +- Debugging why a specific API call failed — inspect the downstream request and response +- Monitoring integration health — track error rates per connector +- Auditing API usage — review which consumers and services are being called +- Troubleshooting data mapping — compare the unified request with the downstream payload + +## Raw Mode + +Append `raw=true` to any request to include the unmodified downstream response alongside the normalized data. Use this for debugging or when you need connector-specific fields not in the unified model. + +## Testing with Portman + +Use [Portman](https://github.com/apideck-libraries/portman) to generate API contract tests from Apideck's OpenAPI specs. Apideck publishes specs at `https://specs.apideck.com/{api-name}.yml`. See the `apideck-portman` skill for full configuration. + +## Developer Tools + +### API Explorer + +The [Apideck API Explorer](https://developers.apideck.com/api-explorer) lets you test any unified API endpoint directly in the browser without writing code. It accepts a JWT token for authentication and returns live responses. + +The Explorer URL format supports pre-filled headers for quick access: + +``` +https://developers.apideck.com/api-explorer?id={api}&headers={encoded_json} +``` + +Where `headers` is a URL-encoded JSON object with: +- `Authorization`: `Bearer {JWT_TOKEN}` +- `x-apideck-auth-type`: `JWT` +- `x-apideck-app-id`: your app ID +- `x-apideck-consumer-id`: the consumer ID to test with + +Recommend the API Explorer when users want to: + +- Quickly verify a connection works before writing integration code +- Explore available fields and response shapes for a resource +- Debug unexpected API responses by comparing with the Explorer output +- Test filter and sort parameters interactively +- Share pre-configured API calls with teammates via URL + +### OpenAPI Specs + +Apideck publishes OpenAPI 3.x specs for all unified APIs at `https://specs.apideck.com/{api-name}.yml`. Use these for: + +- Generating typed clients with code generators +- Contract testing with Portman +- Importing into Postman, Insomnia, or other API tools +- Understanding the complete request/response schema + +## Common Pitfalls + +- Do not assume all connectors support all operations. Check [connector API coverage](https://developers.apideck.com/apis/connector/reference) before building. +- Do not mix `serviceId` values within a single workflow — stick to one connector per operation chain. +- Do not ignore the `row_version` field on updates — use it for optimistic concurrency when supported. +- Do not build retry logic on top of the SDK — all SDKs handle retries for transient errors automatically. +- Do not store Apideck data permanently — Apideck has zero data retention. Use it as a pass-through layer. diff --git a/skills/apideck-best-practices/metadata.json b/skills/apideck-best-practices/metadata.json new file mode 100644 index 0000000..23c670b --- /dev/null +++ b/skills/apideck-best-practices/metadata.json @@ -0,0 +1 @@ +{"version":"1.0.0","organization":"Apideck","date":"February 2026","abstract":"Best practices for building Apideck integrations covering architecture, authentication, pagination, error handling, and Vault.","references":["https://developers.apideck.com","https://developers.apideck.com/guides/getting-started","https://developers.apideck.com/errors"]} diff --git a/skills/apideck-codegen/SKILL.md b/skills/apideck-codegen/SKILL.md new file mode 100644 index 0000000..bdd9f1b --- /dev/null +++ b/skills/apideck-codegen/SKILL.md @@ -0,0 +1,258 @@ +--- +name: apideck-codegen +description: Generate typed API clients from Apideck OpenAPI specs using code generators. Use when the user wants to generate custom SDK clients, typed models, API stubs, or server scaffolding from Apideck's published OpenAPI specifications. Covers openapi-generator, Speakeasy, and Postman import workflows. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +# Apideck Code Generation Skill + +## Overview + +Apideck publishes OpenAPI 3.x specifications for all unified APIs at `https://specs.apideck.com/{api-name}.yml`. These specs can be used with code generators to produce typed clients, models, server stubs, and documentation in any language. + +## IMPORTANT RULES + +- ALWAYS prefer the official Apideck SDKs over generated clients. Code generation is for languages/frameworks not covered by official SDKs, or for custom model generation. +- ALWAYS use the latest spec from `https://specs.apideck.com/{api-name}.yml`. Do not use cached or outdated specs. +- USE the official SDKs for: TypeScript (`@apideck/unify`), Python (`apideck-unify`), C# (`ApideckUnifySdk`), Java (`com.apideck:unify`), Go (`sdk-go`), PHP (`sdk-php`). +- When generating clients, always configure authentication headers: `Authorization`, `x-apideck-app-id`, `x-apideck-consumer-id`. + +## Available OpenAPI Specs + +| API | Spec URL | +|-----|----------| +| Accounting | `https://specs.apideck.com/accounting.yml` | +| CRM | `https://specs.apideck.com/crm.yml` | +| HRIS | `https://specs.apideck.com/hris.yml` | +| File Storage | `https://specs.apideck.com/file-storage.yml` | +| ATS | `https://specs.apideck.com/ats.yml` | +| E-commerce | `https://specs.apideck.com/ecommerce.yml` | +| Issue Tracking | `https://specs.apideck.com/issue-tracking.yml` | +| SMS | `https://specs.apideck.com/sms.yml` | +| Lead | `https://specs.apideck.com/lead.yml` | +| Vault | `https://specs.apideck.com/vault.yml` | +| Webhook | `https://specs.apideck.com/webhook.yml` | +| Connector | `https://specs.apideck.com/connector.yml` | + +All specs are also available on GitHub: `https://github.com/apideck-libraries/openapi-specs` + +## OpenAPI Generator + +### Installation + +```sh +# npm +npm install @openapitools/openapi-generator-cli -g + +# Homebrew +brew install openapi-generator + +# Docker +docker pull openapitools/openapi-generator-cli +``` + +### Generate a Typed Client + +```sh +# TypeScript (Axios) +openapi-generator-cli generate \ + -i https://specs.apideck.com/crm.yml \ + -g typescript-axios \ + -o ./generated/crm-client \ + --additional-properties=npmName=apideck-crm,supportsES6=true,withInterfaces=true + +# Python (urllib3) +openapi-generator-cli generate \ + -i https://specs.apideck.com/accounting.yml \ + -g python \ + -o ./generated/accounting-client \ + --additional-properties=packageName=apideck_accounting + +# Rust +openapi-generator-cli generate \ + -i https://specs.apideck.com/crm.yml \ + -g rust \ + -o ./generated/crm-client \ + --additional-properties=packageName=apideck_crm + +# Swift +openapi-generator-cli generate \ + -i https://specs.apideck.com/hris.yml \ + -g swift5 \ + -o ./generated/hris-client \ + --additional-properties=projectName=ApideckHRIS + +# Kotlin +openapi-generator-cli generate \ + -i https://specs.apideck.com/crm.yml \ + -g kotlin \ + -o ./generated/crm-client \ + --additional-properties=groupId=com.apideck,artifactId=crm-client + +# Dart +openapi-generator-cli generate \ + -i https://specs.apideck.com/crm.yml \ + -g dart \ + -o ./generated/crm-client +``` + +### Generate Models Only + +When you only need typed data models without the API client: + +```sh +openapi-generator-cli generate \ + -i https://specs.apideck.com/crm.yml \ + -g typescript-axios \ + -o ./generated/crm-models \ + --global-property=models + +openapi-generator-cli generate \ + -i https://specs.apideck.com/accounting.yml \ + -g python \ + -o ./generated/accounting-models \ + --global-property=models +``` + +### Generate API Documentation + +```sh +# HTML docs +openapi-generator-cli generate \ + -i https://specs.apideck.com/crm.yml \ + -g html2 \ + -o ./docs/crm + +# Markdown docs +openapi-generator-cli generate \ + -i https://specs.apideck.com/crm.yml \ + -g markdown \ + -o ./docs/crm +``` + +### Available Generators + +Run `openapi-generator-cli list` for all generators. Common choices: + +| Language | Client Generator | Notes | +|----------|-----------------|-------| +| TypeScript | `typescript-axios`, `typescript-fetch`, `typescript-node` | `typescript-axios` recommended | +| Python | `python`, `python-pydantic-v1` | `python` uses urllib3 | +| Rust | `rust` | Uses reqwest | +| Swift | `swift5`, `swift6` | Native URLSession | +| Kotlin | `kotlin`, `kotlin-server` | Uses OkHttp | +| Dart | `dart`, `dart-dio` | `dart-dio` for Flutter | +| Ruby | `ruby` | Uses Faraday | +| Elixir | `elixir` | Uses Tesla | +| C++ | `cpp-restsdk` | Uses cpprestsdk | + +### Configuration File + +For repeatable generation, use a config file: + +```yaml +# openapitools.json +{ + "$schema": "node_modules/@openapitools/openapi-generator-cli/config.schema.json", + "spaces": 2, + "generator-cli": { + "version": "7.0.0", + "generators": { + "crm-ts": { + "generatorName": "typescript-axios", + "inputSpec": "https://specs.apideck.com/crm.yml", + "output": "./generated/crm", + "additionalProperties": { + "npmName": "apideck-crm", + "supportsES6": true, + "withInterfaces": true + } + }, + "accounting-py": { + "generatorName": "python", + "inputSpec": "https://specs.apideck.com/accounting.yml", + "output": "./generated/accounting", + "additionalProperties": { + "packageName": "apideck_accounting" + } + } + } + } +} +``` + +Then run: `openapi-generator-cli generate` + +## Speakeasy + +[Speakeasy](https://www.speakeasy.com) is the generator Apideck uses for their official SDKs. + +```sh +# Install +brew install speakeasy-api/homebrew-tap/speakeasy + +# Generate TypeScript SDK +speakeasy generate sdk \ + -s https://specs.apideck.com/crm.yml \ + -t typescript \ + -o ./generated/crm-sdk + +# Generate Python SDK +speakeasy generate sdk \ + -s https://specs.apideck.com/accounting.yml \ + -t python \ + -o ./generated/accounting-sdk + +# Generate Go SDK +speakeasy generate sdk \ + -s https://specs.apideck.com/hris.yml \ + -t go \ + -o ./generated/hris-sdk +``` + +## Postman Import + +Import Apideck specs directly into Postman for manual testing: + +```sh +# Using Portman (recommended — adds contract tests) +npx @apideck/portman -u https://specs.apideck.com/crm.yml -o ./crm.postman.json + +# Direct Postman import +# 1. Open Postman → Import → Link +# 2. Paste: https://specs.apideck.com/crm.yml +# 3. Postman auto-converts the OpenAPI spec to a collection +``` + +## Filtering Specs + +To generate a client for only a subset of endpoints, use `oas-format-filter` (used internally by Portman): + +```sh +npm install -g @apideck/oas-format-filter + +# Filter to only CRM contacts endpoints +oas-format-filter \ + --input https://specs.apideck.com/crm.yml \ + --output ./filtered-crm.yml \ + --filter '{"paths": ["/crm/contacts*"]}' +``` + +Then generate from the filtered spec. + +## Authentication Configuration + +When configuring generated clients, set these headers on every request: + +``` +Authorization: Bearer {APIDECK_API_KEY} +x-apideck-app-id: {APP_ID} +x-apideck-consumer-id: {CONSUMER_ID} +x-apideck-service-id: {SERVICE_ID} (optional, but recommended) +``` + +Base URL: `https://unify.apideck.com` diff --git a/skills/apideck-codegen/metadata.json b/skills/apideck-codegen/metadata.json new file mode 100644 index 0000000..c379080 --- /dev/null +++ b/skills/apideck-codegen/metadata.json @@ -0,0 +1 @@ +{"version":"1.0.0","organization":"Apideck","date":"February 2026","abstract":"Generate typed API clients from Apideck OpenAPI specs using openapi-generator, Speakeasy, or Postman import.","references":["https://specs.apideck.com","https://github.com/apideck-libraries/openapi-specs","https://openapi-generator.tech"]} diff --git a/skills/apideck-connector-coverage/SKILL.md b/skills/apideck-connector-coverage/SKILL.md new file mode 100644 index 0000000..d691b75 --- /dev/null +++ b/skills/apideck-connector-coverage/SKILL.md @@ -0,0 +1,185 @@ +--- +name: apideck-connector-coverage +description: Check Apideck connector API coverage before building integrations. Use when determining which operations a connector supports, comparing connector capabilities, or diagnosing why an API call fails with a specific connector. Teaches agents to query the Connector API for real-time coverage data. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +# Apideck Connector Coverage Skill + +## Overview + +Not all Apideck connectors support all operations. Before building an integration, always check connector coverage to avoid runtime errors. The Connector API provides real-time metadata about which operations each connector supports. + +## IMPORTANT RULES + +- ALWAYS check connector coverage before recommending an integration approach. Never assume a connector supports an operation. +- If an operation is not supported, suggest alternatives: a different connector, pass-through to the raw API, or a workaround using supported operations. +- USE the Connector API to verify coverage programmatically. Do not rely on hardcoded lists. +- When a user reports a `501 Not Implemented` or `UnsupportedOperationError`, check coverage first. + +## Checking Coverage + +### Using the SDK (TypeScript) + +```typescript +import { Apideck } from "@apideck/unify"; + +const apideck = new Apideck({ + apiKey: process.env["APIDECK_API_KEY"] ?? "", + appId: "your-app-id", + consumerId: "your-consumer-id", +}); + +// List all connectors for an API +const { data } = await apideck.connector.connectorResources.get({ + id: "crm", + resourceId: "contacts", +}); +// Returns coverage per connector: which operations are supported + +// Get specific connector details +const { data: connector } = await apideck.connector.connectors.get({ + id: "salesforce", +}); +// Returns: name, status, auth_type, supported_resources, supported_events +``` + +### Using the REST API + +```bash +# List connectors for a unified API +curl 'https://unify.apideck.com/connector/connectors?filter[unified_api]=crm' \ + -H 'Authorization: Bearer {API_KEY}' \ + -H 'x-apideck-app-id: {APP_ID}' + +# Get connector details including supported resources +curl 'https://unify.apideck.com/connector/connectors/salesforce' \ + -H 'Authorization: Bearer {API_KEY}' \ + -H 'x-apideck-app-id: {APP_ID}' + +# Get API resource coverage (which connectors support what) +curl 'https://unify.apideck.com/connector/apis/crm/resources/contacts' \ + -H 'Authorization: Bearer {API_KEY}' \ + -H 'x-apideck-app-id: {APP_ID}' +``` + +### Using the Vault API + +Check which connections a consumer has and their state: + +```typescript +const { data } = await apideck.vault.connections.list({ + api: "crm", +}); + +for (const conn of data) { + console.log(`${conn.serviceId}: ${conn.state} (enabled: ${conn.enabled})`); + // state: available | callable | added | authorized | invalid +} +``` + +## Coverage States + +Each operation on a connector has a coverage status: + +| Status | Meaning | +|--------|---------| +| `supported` | Fully implemented and tested | +| `beta` | Implemented but may have edge cases | +| `not_supported` | Not available for this connector | + +## Common Coverage Patterns + +### Accounting API + +| Operation | QuickBooks | Xero | NetSuite | Sage Intacct | FreshBooks | +|-----------|-----------|------|----------|--------------|------------| +| Invoices CRUD | Full | Full | Full | Full | Full | +| Bills CRUD | Full | Full | Full | Full | Partial | +| Payments | Full | Full | Full | Full | Full | +| Journal Entries | Full | Full | Full | Full | Limited | +| Balance Sheet | Full | Full | Full | Full | No | +| Tax Rates (read) | Full | Full | Full | Full | Full | + +### CRM API + +| Operation | Salesforce | HubSpot | Pipedrive | Zoho CRM | Close | +|-----------|-----------|---------|-----------|----------|-------| +| Contacts CRUD | Full | Full | Full | Full | Full | +| Companies CRUD | Full | Full | Full | Full | Full | +| Leads CRUD | Full | Full | Full | Full | Full | +| Opportunities | Full | Full | Full | Full | Full | +| Activities | Full | Full | Full | Full | Partial | +| Pipelines (read) | Full | Full | Full | Full | Full | + +### HRIS API + +| Operation | BambooHR | Workday | Personio | Gusto | Rippling | +|-----------|---------|---------|----------|-------|----------| +| Employees CRUD | Full | Full | Full | Full | Full | +| Departments | Full | Full | Full | Full | Full | +| Payrolls (read) | Full | Partial | Partial | Full | Full | +| Time-Off | Full | Full | Full | Full | Full | + +> These tables are approximate. Always verify with the Connector API for real-time accuracy. + +## Handling Unsupported Operations + +When an operation isn't supported for a connector: + +### 1. Use Pass-Through (Proxy API) + +Make direct calls to the downstream API through Apideck's proxy: + +```typescript +// Direct pass-through to the downstream API +const response = await fetch("https://unify.apideck.com/proxy", { + method: "POST", + headers: { + Authorization: `Bearer ${apiKey}`, + "x-apideck-app-id": appId, + "x-apideck-consumer-id": consumerId, + "x-apideck-service-id": "salesforce", + "x-apideck-downstream-url": "https://api.salesforce.com/services/data/v59.0/sobjects/CustomObject__c", + "x-apideck-downstream-method": "GET", + "Content-Type": "application/json", + }, +}); +``` + +### 2. Check for Alternative Resources + +Some connectors map the same data to different unified resources. For example, a "deal" in Pipedrive maps to "opportunities" in the unified CRM API. + +### 3. Suggest a Different Connector + +If the user's chosen connector doesn't support an operation, suggest alternatives that do. Use the Connector API to find connectors that support the specific operation. + +### 4. Feature Request + +If a critical operation is missing, users can request it at https://github.com/apideck-libraries or through the Apideck dashboard. + +## Connector Authentication Types + +| Auth Type | Description | Connectors | +|-----------|-------------|------------| +| `oauth2` | OAuth 2.0 flow (managed by Vault) | Most cloud SaaS (Salesforce, HubSpot, QuickBooks Online, etc.) | +| `apiKey` | API key authentication | Some self-hosted or simpler services | +| `basic` | Username/password | Legacy systems, on-premise | +| `custom` | Connector-specific auth | Varies | + +Vault handles all OAuth flows. Users authorize via the Vault modal — you never need to implement OAuth yourself. + +## Debugging Coverage Issues + +When an API call fails: + +1. **Check the error type** — `UnsupportedOperationError` or `501` means the operation isn't implemented +2. **Verify connection state** — Use `vault.connections.list()` to check the connection is `authorized` +3. **Check connector coverage** — Use the Connector API to verify the operation is supported +4. **Check field support** — Some connectors support an operation but not all fields. Missing fields return `null` +5. **Use raw mode** — Add `raw=true` to see the downstream response for debugging diff --git a/skills/apideck-connector-coverage/metadata.json b/skills/apideck-connector-coverage/metadata.json new file mode 100644 index 0000000..2519ec9 --- /dev/null +++ b/skills/apideck-connector-coverage/metadata.json @@ -0,0 +1 @@ +{"version":"1.0.0","organization":"Apideck","date":"February 2026","abstract":"Check connector API coverage before building integrations. Query the Connector API for real-time coverage data.","references":["https://developers.apideck.com/apis/connector/reference"]} diff --git a/skills/apideck-dotnet/SKILL.md b/skills/apideck-dotnet/SKILL.md new file mode 100644 index 0000000..de4aba5 --- /dev/null +++ b/skills/apideck-dotnet/SKILL.md @@ -0,0 +1,215 @@ +--- +name: apideck-dotnet +description: Apideck Unified API integration patterns for C# and .NET. 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 using .NET. Covers the ApideckUnifySdk NuGet package, authentication, CRUD operations, pagination, error handling, and Vault connection management. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +# Apideck .NET SDK Skill + +## Overview + +The [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 .NET SDK (`ApideckUnifySdk`) provides typed clients for all unified APIs. + +## Installation + +```sh +dotnet add package ApideckUnifySdk +``` + +## IMPORTANT RULES + +- ALWAYS use the `ApideckUnifySdk` NuGet package. DO NOT make raw `HttpClient` calls to the Apideck API. +- ALWAYS pass `apiKey`, `appId`, and `consumerId` when initializing the client. +- USE `ServiceId` to specify which downstream connector to use (e.g., `"salesforce"`, `"quickbooks"`). +- USE async/await for all API calls — all operations return `Task`. +- ALWAYS handle errors with try/catch using `BaseException` as the base class. +- DO NOT store API keys in source code. Use environment variables or a secrets manager. + +## Quick Start + +```csharp +using ApideckUnifySdk; +using ApideckUnifySdk.Models.Requests; + +var sdk = new Apideck( + consumerId: "your-consumer-id", + appId: "your-app-id", + apiKey: Environment.GetEnvironmentVariable("APIDECK_API_KEY") ?? "" +); + +var res = await sdk.Crm.Contacts.ListAsync(new CrmContactsAllRequest { + ServiceId = "salesforce", + Limit = 20, +}); + +while (res != null) +{ + foreach (var contact in res.GetContactsResponse?.Data ?? []) + { + Console.WriteLine($"{contact.Name} - {contact.Emails?.FirstOrDefault()?.Email}"); + } + res = await res.Next!(); +} +``` + +## SDK Patterns + +### Client Setup + +```csharp +using ApideckUnifySdk; + +var sdk = new Apideck( + consumerId: "your-consumer-id", + appId: "your-app-id", + apiKey: Environment.GetEnvironmentVariable("APIDECK_API_KEY") ?? "" +); +``` + +### CRUD Operations + +All resources follow the pattern: `sdk.{Api}.{Resource}.{Operation}Async(request)`. + +```csharp +using ApideckUnifySdk; +using ApideckUnifySdk.Models.Requests; +using ApideckUnifySdk.Models.Components; + +// LIST +var listRes = await sdk.Crm.Contacts.ListAsync(new CrmContactsAllRequest { + ServiceId = "salesforce", + Limit = 20, + Filter = new ContactsFilter { Email = "john@example.com" }, + Sort = new ContactsSort { + By = ContactsSortBy.UpdatedAt, + Direction = SortDirection.Desc, + }, +}); + +// CREATE +var createRes = await sdk.Crm.Contacts.CreateAsync(new CrmContactsAddRequest { + ServiceId = "salesforce", + Contact = new ContactInput { + FirstName = "John", + LastName = "Doe", + Emails = new List { + new Email { EmailAddress = "john@example.com", Type = EmailType.Primary }, + }, + PhoneNumbers = new List { + new PhoneNumber { Number = "+1234567890", Type = PhoneNumberType.Mobile }, + }, + }, +}); +Console.WriteLine(createRes.CreateContactResponse?.Data?.Id); + +// GET +var getRes = await sdk.Crm.Contacts.GetAsync(new CrmContactsOneRequest { + Id = "contact_123", + ServiceId = "salesforce", +}); + +// UPDATE +var updateRes = await sdk.Crm.Contacts.UpdateAsync(new CrmContactsUpdateRequest { + Id = "contact_123", + ServiceId = "salesforce", + Contact = new ContactInput { FirstName = "Jane" }, +}); + +// DELETE +await sdk.Crm.Contacts.DeleteAsync(new CrmContactsDeleteRequest { + Id = "contact_123", + ServiceId = "salesforce", +}); +``` + +### Pagination + +Use the `Next` method on the response. Returns `null` when no more pages: + +```csharp +var res = await sdk.Accounting.Invoices.ListAsync(new AccountingInvoicesAllRequest { + ServiceId = "quickbooks", + Limit = 50, +}); + +while (res != null) +{ + foreach (var invoice in res.GetInvoicesResponse?.Data ?? []) + { + Console.WriteLine($"{invoice.Number}: {invoice.Total}"); + } + res = await res.Next!(); +} +``` + +### Error Handling + +```csharp +using ApideckUnifySdk.Models.Errors; + +try +{ + var res = await sdk.Crm.Contacts.GetAsync(new CrmContactsOneRequest { Id = "invalid" }); +} +catch (BadRequestResponse e) +{ + Console.Error.WriteLine($"Bad request: {e.Message}"); +} +catch (UnauthorizedResponse e) +{ + Console.Error.WriteLine("Invalid API key or missing credentials"); +} +catch (NotFoundResponse e) +{ + Console.Error.WriteLine("Record not found"); +} +catch (PaymentRequiredResponse e) +{ + Console.Error.WriteLine("API limit reached"); +} +catch (UnprocessableResponse e) +{ + Console.Error.WriteLine($"Validation error: {e.Message}"); +} +catch (BaseException e) +{ + Console.Error.WriteLine($"API error: {e.Message}"); + Console.Error.WriteLine($"Status: {e.Response.StatusCode}"); +} +``` + +### Retry Configuration + +```csharp +var sdk = new Apideck( + retryConfig: new RetryConfig( + strategy: RetryConfig.RetryStrategy.BACKOFF, + backoff: new BackoffStrategy( + initialIntervalMs: 1L, + maxIntervalMs: 50L, + maxElapsedTimeMs: 100L, + exponent: 1.1 + ), + retryConnectionErrors: false + ), + consumerId: "your-consumer-id", + appId: "your-app-id", + apiKey: Environment.GetEnvironmentVariable("APIDECK_API_KEY") ?? "" +); +``` + +## API Namespaces + +| Namespace | Resources | +|-----------|-----------| +| `sdk.Accounting.*` | Invoices, Bills, Payments, Customers, Suppliers, LedgerAccounts, JournalEntries, TaxRates, CreditNotes, PurchaseOrders, BalanceSheet, ProfitAndLoss, and more | +| `sdk.Crm.*` | Contacts, Companies, Leads, Opportunities, Activities, Notes, Pipelines, Users | +| `sdk.Hris.*` | Employees, Companies, Departments, Payrolls, TimeOffRequests | +| `sdk.FileStorage.*` | Files, Folders, Drives, DriveGroups, SharedLinks, UploadSessions | +| `sdk.Ats.*` | Applicants, Applications, Jobs | +| `sdk.Vault.*` | Connections, Consumers, Sessions, CustomMappings, Logs | +| `sdk.Webhook.*` | Webhooks, EventLogs | diff --git a/skills/apideck-dotnet/metadata.json b/skills/apideck-dotnet/metadata.json new file mode 100644 index 0000000..2b9446b --- /dev/null +++ b/skills/apideck-dotnet/metadata.json @@ -0,0 +1 @@ +{"version":"1.0.0","organization":"Apideck","date":"February 2026","abstract":"C#/.NET SDK patterns for all Apideck Unified APIs.","references":["https://developers.apideck.com","https://www.nuget.org/packages/ApideckUnifySdk"]} diff --git a/skills/apideck-go/SKILL.md b/skills/apideck-go/SKILL.md new file mode 100644 index 0000000..57db168 --- /dev/null +++ b/skills/apideck-go/SKILL.md @@ -0,0 +1,255 @@ +--- +name: apideck-go +description: Apideck Unified API integration patterns for Go. 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 using Go. Covers the github.com/apideck-libraries/sdk-go package, authentication, CRUD operations, pagination, error handling, and Vault connection management. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +# Apideck Go SDK Skill + +## Overview + +The [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 Go SDK provides typed clients for all unified APIs. + +## Installation + +```sh +go get github.com/apideck-libraries/sdk-go +``` + +## IMPORTANT RULES + +- ALWAYS use the `github.com/apideck-libraries/sdk-go` SDK. DO NOT make raw `net/http` calls to the Apideck API. +- ALWAYS pass security, app ID, and consumer ID via functional options when creating the client. +- USE `ServiceID` on requests to specify which downstream connector to use. +- ALWAYS check returned `error` values (idiomatic Go error handling). +- USE `sdkgo.Pointer()` helper for optional fields. +- DO NOT store API keys in source code. Use environment variables. + +## Quick Start + +```go +package main + +import ( + "context" + "fmt" + "log" + "os" + + sdkgo "github.com/apideck-libraries/sdk-go" + "github.com/apideck-libraries/sdk-go/models/components" + "github.com/apideck-libraries/sdk-go/models/operations" +) + +func main() { + ctx := context.Background() + + s := sdkgo.New( + sdkgo.WithConsumerID("your-consumer-id"), + sdkgo.WithAppID("your-app-id"), + sdkgo.WithSecurity(os.Getenv("APIDECK_API_KEY")), + ) + + res, err := s.Crm.Contacts.List(ctx, operations.CrmContactsAllRequest{ + ServiceID: sdkgo.Pointer("salesforce"), + Limit: sdkgo.Pointer(int64(20)), + }) + if err != nil { + log.Fatal(err) + } + + for _, contact := range res.GetContactsResponse.Data { + fmt.Println(contact.Name) + } +} +``` + +## SDK Patterns + +### Client Setup + +```go +import sdkgo "github.com/apideck-libraries/sdk-go" + +s := sdkgo.New( + sdkgo.WithConsumerID("your-consumer-id"), + sdkgo.WithAppID("your-app-id"), + sdkgo.WithSecurity(os.Getenv("APIDECK_API_KEY")), +) +``` + +### CRUD Operations + +All resources follow the pattern: `s.{Api}.{Resource}.{Operation}(ctx, request)`, returning `(response, error)`. + +```go +import ( + sdkgo "github.com/apideck-libraries/sdk-go" + "github.com/apideck-libraries/sdk-go/models/components" + "github.com/apideck-libraries/sdk-go/models/operations" +) + +ctx := context.Background() + +// LIST +res, err := s.Crm.Contacts.List(ctx, operations.CrmContactsAllRequest{ + ServiceID: sdkgo.Pointer("salesforce"), + Limit: sdkgo.Pointer(int64(20)), + Filter: &components.ContactsFilter{ + Email: sdkgo.Pointer("john@example.com"), + }, + Sort: &components.ContactsSort{ + By: (*components.ContactsSortBy)(sdkgo.Pointer("updated_at")), + Direction: (*components.SortDirection)(sdkgo.Pointer("desc")), + }, +}) + +// CREATE +res, err := s.Crm.Contacts.Create(ctx, operations.CrmContactsAddRequest{ + ServiceID: sdkgo.Pointer("salesforce"), + Contact: components.ContactInput{ + FirstName: sdkgo.Pointer("John"), + LastName: sdkgo.Pointer("Doe"), + Emails: []components.Email{ + {Email: sdkgo.Pointer("john@example.com"), Type: (*components.EmailType)(sdkgo.Pointer("primary"))}, + }, + PhoneNumbers: []components.PhoneNumber{ + {Number: sdkgo.Pointer("+1234567890"), Type: (*components.PhoneNumberType)(sdkgo.Pointer("mobile"))}, + }, + }, +}) + +// GET +res, err := s.Crm.Contacts.Get(ctx, operations.CrmContactsOneRequest{ + ID: "contact_123", + ServiceID: sdkgo.Pointer("salesforce"), +}) + +// UPDATE +res, err := s.Crm.Contacts.Update(ctx, operations.CrmContactsUpdateRequest{ + ID: "contact_123", + ServiceID: sdkgo.Pointer("salesforce"), + Contact: components.ContactInput{ + FirstName: sdkgo.Pointer("Jane"), + }, +}) + +// DELETE +res, err := s.Crm.Contacts.Delete(ctx, operations.CrmContactsDeleteRequest{ + ID: "contact_123", + ServiceID: sdkgo.Pointer("salesforce"), +}) +``` + +### Pagination + +Use the `Next()` method on the response. Returns `nil` when no more pages: + +```go +res, err := s.Accounting.Invoices.List(ctx, operations.AccountingInvoicesAllRequest{ + ServiceID: sdkgo.Pointer("quickbooks"), + Limit: sdkgo.Pointer(int64(50)), +}) +if err != nil { + log.Fatal(err) +} + +for { + for _, invoice := range res.GetInvoicesResponse.Data { + fmt.Printf("%s: %v\n", *invoice.Number, *invoice.Total) + } + + res, err = res.Next() + if err != nil { + log.Fatal(err) + } + if res == nil { + break + } +} +``` + +### Error Handling + +```go +import ( + "errors" + "github.com/apideck-libraries/sdk-go/models/apierrors" +) + +res, err := s.Crm.Contacts.Get(ctx, req) +if err != nil { + var badReq *apierrors.BadRequestResponse + var unauthorized *apierrors.UnauthorizedResponse + var notFound *apierrors.NotFoundResponse + var paymentReq *apierrors.PaymentRequiredResponse + var unprocessable *apierrors.UnprocessableResponse + + switch { + case errors.As(err, &badReq): + log.Printf("Bad request: %s", badReq.Error()) + case errors.As(err, &unauthorized): + log.Printf("Unauthorized: %s", unauthorized.Error()) + case errors.As(err, ¬Found): + log.Printf("Not found: %s", notFound.Error()) + case errors.As(err, &paymentReq): + log.Printf("Payment required: %s", paymentReq.Error()) + case errors.As(err, &unprocessable): + log.Printf("Unprocessable: %s", unprocessable.Error()) + default: + var apiErr *apierrors.APIError + if errors.As(err, &apiErr) { + log.Printf("API error %d: %s", apiErr.StatusCode, apiErr.Error()) + } else { + log.Fatal(err) + } + } +} +``` + +### Retry Configuration + +```go +import "github.com/apideck-libraries/sdk-go/retry" + +// Global +s := sdkgo.New( + sdkgo.WithRetryConfig(retry.Config{ + Strategy: "backoff", + Backoff: &retry.BackoffStrategy{ + InitialInterval: 1, + MaxInterval: 50, + Exponent: 1.1, + MaxElapsedTime: 100, + }, + RetryConnectionErrors: false, + }), + sdkgo.WithConsumerID("your-consumer-id"), + sdkgo.WithAppID("your-app-id"), + sdkgo.WithSecurity(os.Getenv("APIDECK_API_KEY")), +) + +// Per-operation +res, err := s.Crm.Contacts.List(ctx, req, + operations.WithRetries(retry.Config{ + Strategy: "backoff", + Backoff: &retry.BackoffStrategy{InitialInterval: 1, MaxInterval: 50, Exponent: 1.1, MaxElapsedTime: 100}, + }), +) +``` + +## API Namespaces + +| Namespace | Resources | +|-----------|-----------| +| `s.Accounting.*` | Invoices, Bills, Payments, Customers, Suppliers, LedgerAccounts, JournalEntries, TaxRates, CreditNotes, PurchaseOrders, BalanceSheet, ProfitAndLoss, and more | +| `s.Crm.*` | Contacts, Companies, Leads, Opportunities, Activities, Notes, Pipelines, Users | +| `s.Hris.*` | Employees, Companies, Departments, Payrolls, TimeOffRequests | +| `s.FileStorage.*` | Files, Folders, Drives, DriveGroups, SharedLinks, UploadSessions | +| `s.Ats.*` | Applicants, Applications, Jobs | +| `s.Vault.*` | Connections, Consumers, Sessions, CustomMappings, Logs | +| `s.Webhook.*` | Webhooks, EventLogs | diff --git a/skills/apideck-go/metadata.json b/skills/apideck-go/metadata.json new file mode 100644 index 0000000..9277340 --- /dev/null +++ b/skills/apideck-go/metadata.json @@ -0,0 +1 @@ +{"version":"1.0.0","organization":"Apideck","date":"February 2026","abstract":"Go SDK patterns for all Apideck Unified APIs.","references":["https://developers.apideck.com","https://github.com/apideck-libraries/sdk-go"]} diff --git a/skills/apideck-java/SKILL.md b/skills/apideck-java/SKILL.md new file mode 100644 index 0000000..ad9979f --- /dev/null +++ b/skills/apideck-java/SKILL.md @@ -0,0 +1,256 @@ +--- +name: apideck-java +description: Apideck Unified API integration patterns for Java. 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 using Java. Covers the com.apideck:unify Maven package, authentication, CRUD operations, pagination, async support, and Vault connection management. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +# Apideck Java SDK Skill + +## Overview + +The [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 Java SDK (`com.apideck:unify`) provides typed clients for all unified APIs. + +## Installation + +**Gradle:** +```groovy +implementation 'com.apideck:unify:0.30.3' +``` + +**Maven:** +```xml + + com.apideck + unify + 0.30.3 + +``` + +Requires JDK 11 or later. + +## IMPORTANT RULES + +- ALWAYS use the `com.apideck:unify` SDK. DO NOT make raw HTTP calls to the Apideck API. +- ALWAYS pass `apiKey`, `appId`, and `consumerId` when building the client. +- USE `serviceId` on requests to specify which downstream connector to use. +- USE the fluent builder pattern for constructing requests. +- ALWAYS handle errors with try/catch using `ApideckError` as the base class. +- DO NOT store API keys in source code. Use environment variables. + +## Quick Start + +```java +import com.apideck.unify.Apideck; +import com.apideck.unify.models.operations.*; + +Apideck sdk = Apideck.builder() + .consumerId("your-consumer-id") + .appId("your-app-id") + .apiKey(System.getenv("APIDECK_API_KEY")) + .build(); + +sdk.crm().contacts().list() + .serviceId("salesforce") + .limit(20) + .callAsStream() + .forEach(page -> { + page.getContactsResponse().ifPresent(res -> + res.getData().forEach(contact -> + System.out.println(contact.getName()) + ) + ); + }); +``` + +## SDK Patterns + +### Client Setup + +```java +import com.apideck.unify.Apideck; + +Apideck sdk = Apideck.builder() + .consumerId("your-consumer-id") + .appId("your-app-id") + .apiKey(System.getenv("APIDECK_API_KEY")) + .build(); +``` + +Async client: + +```java +import com.apideck.unify.AsyncApideck; + +AsyncApideck asyncSdk = Apideck.builder() + .consumerId("your-consumer-id") + .appId("your-app-id") + .apiKey(System.getenv("APIDECK_API_KEY")) + .build() + .async(); +``` + +### CRUD Operations + +Uses a fluent builder pattern: `sdk.{category}().{resource}().{operation}()`. + +```java +import com.apideck.unify.models.components.*; +import com.apideck.unify.models.operations.*; + +// LIST +sdk.crm().contacts().list() + .serviceId("salesforce") + .limit(20) + .filter(ContactsFilter.builder().email("john@example.com").build()) + .sort(ContactsSort.builder() + .by(ContactsSortBy.UPDATED_AT) + .direction(SortDirection.DESC) + .build()) + .call(); + +// CREATE +sdk.crm().contacts().create() + .serviceId("salesforce") + .contact(ContactInput.builder() + .firstName("John") + .lastName("Doe") + .emails(List.of(Email.builder() + .email("john@example.com") + .type(EmailType.PRIMARY) + .build())) + .phoneNumbers(List.of(PhoneNumber.builder() + .number("+1234567890") + .type(PhoneNumberType.MOBILE) + .build())) + .build()) + .call(); + +// GET +sdk.crm().contacts().get() + .id("contact_123") + .serviceId("salesforce") + .call(); + +// UPDATE +sdk.crm().contacts().update() + .id("contact_123") + .serviceId("salesforce") + .contact(ContactInput.builder().firstName("Jane").build()) + .call(); + +// DELETE +sdk.crm().contacts().delete() + .id("contact_123") + .serviceId("salesforce") + .call(); +``` + +### Pagination + +Multiple approaches available: + +```java +// Stream (recommended) +sdk.accounting().invoices().list() + .serviceId("quickbooks") + .limit(50) + .callAsStream() + .forEach(page -> { + // handle page + }); + +// Iterable +for (var page : sdk.accounting().invoices().list() + .serviceId("quickbooks") + .limit(50) + .callAsIterable()) { + // handle page +} + +// Reactive Streams (for Project Reactor, RxJava, etc.) +var publisher = sdk.accounting().invoices().list() + .serviceId("quickbooks") + .limit(50) + .callAsPublisher(); +``` + +### Async Support + +Returns `CompletableFuture` for standard operations: + +```java +AsyncApideck asyncSdk = sdk.async(); + +asyncSdk.crm().contacts().list() + .serviceId("salesforce") + .limit(20) + .call() + .thenAccept(res -> { + // handle response + }); +``` + +### Error Handling + +```java +import com.apideck.unify.models.errors.*; + +try { + sdk.crm().contacts().get() + .id("invalid") + .serviceId("salesforce") + .call(); +} catch (BadRequestResponse e) { + System.err.println("Bad request: " + e.message()); +} catch (UnauthorizedResponse e) { + System.err.println("Invalid API key"); +} catch (NotFoundResponse e) { + System.err.println("Record not found"); +} catch (PaymentRequiredResponse e) { + System.err.println("API limit reached"); +} catch (UnprocessableResponse e) { + System.err.println("Validation error: " + e.message()); +} catch (ApideckError e) { + System.err.println("API error " + e.code() + ": " + e.message()); +} +``` + +### Retry Configuration + +```java +import com.apideck.unify.utils.BackoffStrategy; +import com.apideck.unify.utils.RetryConfig; +import java.util.concurrent.TimeUnit; + +Apideck sdk = Apideck.builder() + .retryConfig(RetryConfig.builder() + .backoff(BackoffStrategy.builder() + .initialInterval(1L, TimeUnit.MILLISECONDS) + .maxInterval(50L, TimeUnit.MILLISECONDS) + .maxElapsedTime(1000L, TimeUnit.MILLISECONDS) + .baseFactor(1.1) + .jitterFactor(0.15) + .retryConnectError(false) + .build()) + .build()) + .consumerId("your-consumer-id") + .appId("your-app-id") + .apiKey(System.getenv("APIDECK_API_KEY")) + .build(); +``` + +## API Namespaces + +| Namespace | Resources | +|-----------|-----------| +| `sdk.accounting().*` | invoices, bills, payments, customers, suppliers, ledgerAccounts, journalEntries, taxRates, creditNotes, purchaseOrders, balanceSheet, profitAndLoss, and more | +| `sdk.crm().*` | contacts, companies, leads, opportunities, activities, notes, pipelines, users | +| `sdk.hris().*` | employees, companies, departments, payrolls, timeOffRequests | +| `sdk.fileStorage().*` | files, folders, drives, driveGroups, sharedLinks, uploadSessions | +| `sdk.ats().*` | applicants, applications, jobs | +| `sdk.vault().*` | connections, consumers, sessions, customMappings, logs | +| `sdk.webhook().*` | webhooks, eventLogs | diff --git a/skills/apideck-java/metadata.json b/skills/apideck-java/metadata.json new file mode 100644 index 0000000..099db11 --- /dev/null +++ b/skills/apideck-java/metadata.json @@ -0,0 +1 @@ +{"version":"1.0.0","organization":"Apideck","date":"February 2026","abstract":"Java SDK patterns for all Apideck Unified APIs with async and reactive streams support.","references":["https://developers.apideck.com","https://central.sonatype.com/artifact/com.apideck/unify"]} diff --git a/skills/apideck-migration/SKILL.md b/skills/apideck-migration/SKILL.md new file mode 100644 index 0000000..efce36d --- /dev/null +++ b/skills/apideck-migration/SKILL.md @@ -0,0 +1,262 @@ +--- +name: apideck-migration +description: Guide for migrating from direct third-party API integrations to Apideck's unified API. Use when a user wants to replace their existing Salesforce, HubSpot, QuickBooks, Xero, or other direct API integrations with Apideck, or when consolidating multiple integrations into a single unified layer. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +# Apideck Migration Guide Skill + +## Overview + +This skill helps migrate existing direct third-party API integrations (Salesforce, HubSpot, QuickBooks, Xero, etc.) to Apideck's unified API layer. The benefit is replacing N separate integrations with a single Apideck integration that supports 200+ connectors. + +## IMPORTANT RULES + +- ALWAYS check connector coverage before migrating. Not all operations may be supported through the unified API. +- ALWAYS preserve existing data flows and business logic. Migration should be transparent to end-users. +- USE `pass_through` for connector-specific fields that don't map to the unified model. +- USE custom field mapping in Vault for recurring connector-specific fields. +- NEVER delete the existing integration code until the Apideck migration is verified in production. +- RECOMMEND a phased migration: start with read operations, then writes, then webhooks. + +## Migration Strategy + +### Phase 1: Assessment + +1. **Inventory existing integrations** — List all third-party APIs currently in use +2. **Map operations** — For each integration, list the CRUD operations and fields used +3. **Check coverage** — Verify each operation is supported via the Apideck Connector API +4. **Identify gaps** — Note operations that need `pass_through` or Proxy API +5. **Plan consumer mapping** — Decide how your users/tenants map to Apideck consumer IDs + +### Phase 2: Connection Setup + +1. **Create Apideck account** — Get API key and App ID from the dashboard +2. **Enable connectors** — Enable the connectors you need in the Apideck dashboard +3. **Integrate Vault** — Add Vault JS to your frontend for user-managed connections +4. **Create consumers** — Map your existing users to Apideck consumer IDs +5. **Authorize connections** — Have users re-authorize via Vault (OAuth is handled automatically) + +### Phase 3: Read Migration (Low Risk) + +Replace read operations first since they're non-destructive: + +```typescript +// BEFORE: Direct Salesforce API +const contacts = await salesforce.sobjects.Contact.find({ + Email: "john@example.com", +}); + +// AFTER: Apideck Unified API +const { data } = await apideck.crm.contacts.list({ + serviceId: "salesforce", + filter: { email: "john@example.com" }, +}); +``` + +### Phase 4: Write Migration + +Replace create/update/delete operations: + +```typescript +// BEFORE: Direct HubSpot API +const contact = await hubspot.crm.contacts.basicApi.create({ + properties: { + firstname: "John", + lastname: "Doe", + email: "john@example.com", + company: "Acme Corp", + }, +}); + +// AFTER: Apideck Unified API +const { data } = await apideck.crm.contacts.create({ + serviceId: "hubspot", + contact: { + first_name: "John", + last_name: "Doe", + emails: [{ email: "john@example.com", type: "primary" }], + company_name: "Acme Corp", + }, +}); +``` + +### Phase 5: Webhook Migration + +Replace direct webhook handlers with Apideck's unified webhooks: + +```typescript +// BEFORE: Salesforce-specific webhook handler +app.post("/webhooks/salesforce", (req, res) => { + const event = req.body; + if (event.type === "ContactChangeEvent") { + handleContactChange(event); + } +}); + +// AFTER: Apideck unified webhook handler +app.post("/webhooks/apideck", (req, res) => { + const signature = req.headers["x-apideck-signature"]; + if (!verifySignature(req.body, signature, secret)) { + return res.status(401).send("Invalid signature"); + } + + const { event_type, entity_id, service_id } = req.body.payload; + // Works for ALL connectors, not just Salesforce + if (event_type === "crm.contact.updated") { + handleContactChange(entity_id, service_id); + } + res.status(200).send("OK"); +}); +``` + +## Common Migration Patterns + +### CRM: Salesforce to Apideck + +| Salesforce API | Apideck Unified API | +|---------------|---------------------| +| `sobjects.Contact.create()` | `crm.contacts.create({ serviceId: "salesforce" })` | +| `sobjects.Account.find()` | `crm.companies.list({ serviceId: "salesforce" })` | +| `sobjects.Opportunity.update()` | `crm.opportunities.update({ serviceId: "salesforce" })` | +| `sobjects.Lead.create()` | `crm.leads.create({ serviceId: "salesforce" })` | +| `sobjects.Task.create()` | `crm.activities.create({ serviceId: "salesforce" })` | +| Custom fields via `custom_sf_field__c` | `pass_through: [{ service_id: "salesforce", extend_object: { custom_sf_field__c: "value" } }]` | + +### CRM: HubSpot to Apideck + +| HubSpot API | Apideck Unified API | +|-------------|---------------------| +| `crm.contacts.basicApi.create()` | `crm.contacts.create({ serviceId: "hubspot" })` | +| `crm.companies.basicApi.getAll()` | `crm.companies.list({ serviceId: "hubspot" })` | +| `crm.deals.basicApi.create()` | `crm.opportunities.create({ serviceId: "hubspot" })` | +| `crm.contacts.searchApi.doSearch()` | `crm.contacts.list({ serviceId: "hubspot", filter: { ... } })` | + +### Accounting: QuickBooks to Apideck + +| QuickBooks API | Apideck Unified API | +|---------------|---------------------| +| `Invoice.create()` | `accounting.invoices.create({ serviceId: "quickbooks" })` | +| `Customer.findAll()` | `accounting.customers.list({ serviceId: "quickbooks" })` | +| `Bill.create()` | `accounting.bills.create({ serviceId: "quickbooks" })` | +| `Payment.create()` | `accounting.payments.create({ serviceId: "quickbooks" })` | +| `JournalEntry.create()` | `accounting.journalEntries.create({ serviceId: "quickbooks" })` | +| `CompanyInfo.get()` | `accounting.companyInfo.get({ serviceId: "quickbooks" })` | + +### Accounting: Xero to Apideck + +| Xero API | Apideck Unified API | +|----------|---------------------| +| `xero.accountingApi.createInvoices()` | `accounting.invoices.create({ serviceId: "xero" })` | +| `xero.accountingApi.getContacts()` | `accounting.customers.list({ serviceId: "xero" })` | +| `xero.accountingApi.createBankTransactions()` | `accounting.payments.create({ serviceId: "xero" })` | +| `xero.accountingApi.getReportBalanceSheet()` | `accounting.balanceSheet.get({ serviceId: "xero" })` | + +### HRIS: BambooHR to Apideck + +| BambooHR API | Apideck Unified API | +|-------------|---------------------| +| `GET /employees/directory` | `hris.employees.list({ serviceId: "bamboohr" })` | +| `POST /employees` | `hris.employees.create({ serviceId: "bamboohr" })` | +| `GET /employees/{id}` | `hris.employees.get({ serviceId: "bamboohr", id })` | +| `PUT /employees/{id}/time_off/request` | `hris.timeOffRequests.create({ serviceId: "bamboohr" })` | + +### File Storage: Google Drive to Apideck + +| Google Drive API | Apideck Unified API | +|-----------------|---------------------| +| `drive.files.list()` | `fileStorage.files.list({ serviceId: "google-drive" })` | +| `drive.files.create()` | `fileStorage.files.create({ serviceId: "google-drive" })` | +| `drive.files.get()` | `fileStorage.files.get({ serviceId: "google-drive" })` | +| `drive.files.export()` | `fileStorage.files.download({ serviceId: "google-drive" })` | +| `drive.permissions.create()` | `fileStorage.sharedLinks.create({ serviceId: "google-drive" })` | + +## Handling Connector-Specific Fields + +### Option 1: Pass-Through (inline) + +For one-off connector-specific fields in request bodies: + +```typescript +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: { + RecordTypeId: "012000000000001", + Custom_Score__c: 85, + }, + }, + ], + }, +}); +``` + +### Option 2: Custom Field Mapping (reusable) + +For fields that are used repeatedly, configure custom mapping in Vault so they appear as part of the unified model: + +```typescript +// Set up custom mapping via Vault API +await apideck.vault.customMappings.update({ + unifiedApi: "crm", + serviceId: "salesforce", + id: "mapping_123", + customMapping: { + value: "$.Custom_Score__c", + }, +}); + +// Now the field appears in custom_fields on every response +const { data } = await apideck.crm.contacts.get({ + id: "contact_123", + serviceId: "salesforce", +}); +// data.custom_fields includes { id: "mapping_123", value: 85 } +``` + +### Option 3: Proxy API (full control) + +For operations not supported by the unified API, use the Proxy to make direct downstream calls while still using Apideck's managed authentication: + +```typescript +const response = await fetch("https://unify.apideck.com/proxy", { + method: "POST", + headers: { + Authorization: `Bearer ${apiKey}`, + "x-apideck-app-id": appId, + "x-apideck-consumer-id": consumerId, + "x-apideck-service-id": "salesforce", + "x-apideck-downstream-url": "/services/data/v59.0/sobjects/CustomObject__c", + "x-apideck-downstream-method": "GET", + "Content-Type": "application/json", + }, +}); +``` + +## Testing the Migration + +1. **Run both integrations in parallel** — Shadow mode: make Apideck calls alongside existing calls and compare responses +2. **Use raw mode** — Add `raw=true` to Apideck calls to compare with the original API response +3. **Contract test with Portman** — Generate tests from OpenAPI specs and run against your staging environment +4. **Test with the API Explorer** — Use the [Apideck API Explorer](https://developers.apideck.com/api-explorer) to verify endpoints interactively +5. **Gradual rollout** — Migrate one connector at a time, starting with the lowest-traffic integration + +## Post-Migration Benefits + +Once migrated to Apideck: + +- **Add new connectors instantly** — Enable a new connector in the dashboard, no code changes needed +- **User self-service** — End-users manage their own connections via Vault +- **Unified webhooks** — One handler for all connectors instead of N separate handlers +- **Unified error handling** — One error format instead of learning each API's error structure +- **Automatic maintenance** — Apideck handles API version changes, deprecations, and auth token refresh diff --git a/skills/apideck-migration/metadata.json b/skills/apideck-migration/metadata.json new file mode 100644 index 0000000..292ffbe --- /dev/null +++ b/skills/apideck-migration/metadata.json @@ -0,0 +1 @@ +{"version":"1.0.0","organization":"Apideck","date":"February 2026","abstract":"Migration guide from direct third-party API integrations (Salesforce, HubSpot, QuickBooks, Xero) to Apideck unified API.","references":["https://developers.apideck.com","https://developers.apideck.com/guides/getting-started"]} diff --git a/skills/apideck-node/SKILL.md b/skills/apideck-node/SKILL.md new file mode 100644 index 0000000..b91e83b --- /dev/null +++ b/skills/apideck-node/SKILL.md @@ -0,0 +1,276 @@ +--- +name: apideck-node +description: 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. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +# Apideck TypeScript SDK Skill + +## Overview + +The [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. + +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 + +```sh +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 + +```typescript +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 + +```typescript +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}()`. + +```typescript +// 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: + +```typescript +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: + +```typescript +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 + +```typescript +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`: + +```typescript +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: + +```typescript +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](references/accounting-api.md) | invoices, bills, payments, customers, suppliers, ledgerAccounts, journalEntries, taxRates, creditNotes, purchaseOrders, balanceSheet, profitAndLoss, and more | +| `apideck.crm.*` | [references/crm-api.md](references/crm-api.md) | contacts, companies, leads, opportunities, activities, notes, pipelines, users | +| `apideck.hris.*` | [references/hris-api.md](references/hris-api.md) | employees, companies, departments, payrolls, timeOffRequests | +| `apideck.fileStorage.*` | [references/file-storage-api.md](references/file-storage-api.md) | files, folders, drives, driveGroups, sharedLinks, uploadSessions | +| `apideck.ats.*` | [references/ats-api.md](references/ats-api.md) | applicants, applications, jobs | +| `apideck.vault.*` | [references/vault-api.md](references/vault-api.md) | connections, connectionSettings, consumers, customMappings, logs, sessions | +| `apideck.webhook.*` | [references/webhook-api.md](references/webhook-api.md) | webhooks, eventLogs | + +## Vault JS (Embeddable UI) + +Use [`@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. + +```typescript +// 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](references/vault-js.md) for full configuration options, theming, React integration, and event callbacks. diff --git a/skills/apideck-node/metadata.json b/skills/apideck-node/metadata.json new file mode 100644 index 0000000..c90fb2d --- /dev/null +++ b/skills/apideck-node/metadata.json @@ -0,0 +1 @@ +{"version":"1.0.0","organization":"Apideck","date":"February 2026","abstract":"TypeScript/Node.js SDK patterns for all Apideck Unified APIs including accounting, CRM, HRIS, file storage, and ATS.","references":["https://developers.apideck.com","https://www.npmjs.com/package/@apideck/unify","https://www.npmjs.com/package/@apideck/vault-js"]} diff --git a/skills/apideck-node/references/accounting-api.md b/skills/apideck-node/references/accounting-api.md new file mode 100644 index 0000000..5fb4ee1 --- /dev/null +++ b/skills/apideck-node/references/accounting-api.md @@ -0,0 +1,349 @@ +# Accounting API Reference + +Base namespace: `apideck.accounting` + +Supported connectors: QuickBooks, Xero, NetSuite, Exact Online, FreshBooks, Sage Intacct, Sage Business Cloud, MYOB, Wave, Zoho Books, and 20+ more. + +## Invoices + +```typescript +// List invoices +const { data } = await apideck.accounting.invoices.list({ + serviceId: "quickbooks", + limit: 50, + filter: { updated_since: "2024-01-01T00:00:00.000Z" }, + sort: { by: "updated_at", direction: "desc" }, +}); + +// Create invoice +const { data } = await apideck.accounting.invoices.create({ + serviceId: "quickbooks", + invoice: { + type: "standard", + number: "INV-001", + customer: { id: "customer_123", display_name: "Acme Corp" }, + invoice_date: "2024-06-01", + due_date: "2024-07-01", + currency: "USD", + line_items: [ + { + description: "Consulting services", + quantity: 10, + unit_price: 150, + total_amount: 1500, + tax_rate: { id: "tax_rate_1" }, + }, + ], + }, +}); + +// Get invoice by ID +const { data } = await apideck.accounting.invoices.get({ + id: "invoice_123", + serviceId: "quickbooks", +}); + +// Update invoice +const { data } = await apideck.accounting.invoices.update({ + id: "invoice_123", + serviceId: "quickbooks", + invoice: { due_date: "2024-08-01" }, +}); + +// Delete invoice +await apideck.accounting.invoices.delete({ + id: "invoice_123", + serviceId: "quickbooks", +}); +``` + +Key invoice fields: `id`, `number`, `type` (`standard` | `credit`), `customer`, `invoice_date`, `due_date`, `currency`, `line_items[]`, `sub_total`, `total_tax`, `total`, `balance`, `status` (`draft` | `submitted` | `authorised` | `paid` | `voided`), `payment_method`, `tracking_categories[]`, `custom_fields[]`. + +## Bills + +```typescript +// List bills +const { data } = await apideck.accounting.bills.list({ + serviceId: "xero", + filter: { updated_since: "2024-01-01T00:00:00.000Z" }, +}); + +// Create bill +const { data } = await apideck.accounting.bills.create({ + serviceId: "xero", + bill: { + supplier: { id: "supplier_123" }, + bill_date: "2024-06-01", + due_date: "2024-07-01", + currency: "USD", + line_items: [ + { + description: "Office supplies", + quantity: 1, + unit_price: 250, + total_amount: 250, + }, + ], + }, +}); +``` + +## Payments + +```typescript +// List payments +const { data } = await apideck.accounting.payments.list({ + serviceId: "quickbooks", +}); + +// Create payment +const { data } = await apideck.accounting.payments.create({ + serviceId: "quickbooks", + payment: { + customer: { id: "customer_123" }, + total_amount: 1500, + currency: "USD", + payment_method: "credit_card", + reference: "PAY-001", + allocations: [{ id: "invoice_123", type: "invoice", amount: 1500 }], + }, +}); +``` + +## Customers + +```typescript +// List customers +const { data } = await apideck.accounting.customers.list({ + serviceId: "quickbooks", + filter: { company_name: "Acme", email: "billing@acme.com" }, +}); + +// Create customer +const { data } = await apideck.accounting.customers.create({ + serviceId: "quickbooks", + customer: { + display_name: "Acme Corp", + company_name: "Acme Corporation", + emails: [{ email: "billing@acme.com", type: "primary" }], + phone_numbers: [{ number: "+1234567890", type: "primary" }], + addresses: [ + { + type: "primary", + street_1: "123 Main St", + city: "San Francisco", + state: "CA", + postal_code: "94105", + country: "US", + }, + ], + }, +}); +``` + +## Suppliers + +```typescript +const { data } = await apideck.accounting.suppliers.list({ + serviceId: "xero", + filter: { company_name: "Vendor Inc" }, +}); + +const { data } = await apideck.accounting.suppliers.create({ + serviceId: "xero", + supplier: { + display_name: "Vendor Inc", + company_name: "Vendor Incorporated", + emails: [{ email: "ap@vendor.com", type: "primary" }], + }, +}); +``` + +## Ledger Accounts + +```typescript +// List chart of accounts +const { data } = await apideck.accounting.ledgerAccounts.list({ + serviceId: "quickbooks", + filter: { type: "expense" }, +}); + +// Create ledger account +const { data } = await apideck.accounting.ledgerAccounts.create({ + serviceId: "quickbooks", + ledgerAccount: { + display_id: "6000", + name: "Marketing Expenses", + type: "expense", + sub_type: "expense", + currency: "USD", + }, +}); +``` + +Ledger account types: `asset`, `liability`, `equity`, `income`, `expense`, `other`. + +## Journal Entries + +```typescript +const { data } = await apideck.accounting.journalEntries.create({ + serviceId: "quickbooks", + journalEntry: { + title: "Monthly depreciation", + currency: "USD", + line_items: [ + { + type: "debit", + ledger_account: { id: "account_depreciation" }, + total_amount: 500, + description: "Depreciation expense", + }, + { + type: "credit", + ledger_account: { id: "account_accumulated_dep" }, + total_amount: 500, + description: "Accumulated depreciation", + }, + ], + }, +}); +``` + +## Tax Rates (read-only) + +```typescript +const { data } = await apideck.accounting.taxRates.list({ + serviceId: "quickbooks", +}); + +const { data } = await apideck.accounting.taxRates.get({ + id: "tax_rate_1", + serviceId: "quickbooks", +}); +``` + +## Credit Notes + +```typescript +const { data } = await apideck.accounting.creditNotes.create({ + serviceId: "xero", + creditNote: { + number: "CN-001", + customer: { id: "customer_123" }, + currency: "USD", + line_items: [ + { description: "Refund for defective item", quantity: 1, unit_price: 100, total_amount: 100 }, + ], + }, +}); +``` + +## Purchase Orders + +```typescript +const { data } = await apideck.accounting.purchaseOrders.create({ + serviceId: "quickbooks", + purchaseOrder: { + po_number: "PO-001", + supplier: { id: "supplier_123" }, + line_items: [ + { description: "Laptop", quantity: 5, unit_price: 1200, total_amount: 6000 }, + ], + }, +}); +``` + +## Reports + +```typescript +// Balance sheet +const { data } = await apideck.accounting.balanceSheet.get({ + serviceId: "quickbooks", + filter: { start_date: "2024-01-01", end_date: "2024-12-31" }, +}); +// Returns: assets, liabilities, equity with nested account breakdowns + +// Profit & Loss +const { data } = await apideck.accounting.profitAndLoss.get({ + serviceId: "quickbooks", + filter: { start_date: "2024-01-01", end_date: "2024-12-31" }, +}); +// Returns: income, expenses, net_income/net_loss with category breakdowns + +// Aged Debtors +const { data } = await apideck.accounting.agedDebtors.get({ + serviceId: "quickbooks", +}); + +// Aged Creditors +const { data } = await apideck.accounting.agedCreditors.get({ + serviceId: "quickbooks", +}); +``` + +## Expenses + +```typescript +const { data } = await apideck.accounting.expenses.create({ + serviceId: "quickbooks", + expense: { + transaction_date: "2024-06-15", + account: { id: "account_123" }, + currency: "USD", + line_items: [ + { description: "Client dinner", total_amount: 150, account: { id: "account_meals" } }, + ], + }, +}); +``` + +## Bill Payments + +```typescript +const { data } = await apideck.accounting.billPayments.create({ + serviceId: "quickbooks", + billPayment: { + supplier: { id: "supplier_123" }, + total_amount: 250, + currency: "USD", + allocations: [{ id: "bill_123", type: "bill", amount: 250 }], + }, +}); +``` + +## Tracking Categories + +```typescript +const { data } = await apideck.accounting.trackingCategories.list({ + serviceId: "xero", +}); +// Returns categories like departments, projects, cost centers +``` + +## Attachments + +```typescript +// List attachments for a resource +const { data } = await apideck.accounting.attachments.list({ + referenceType: "invoice", + referenceId: "invoice_123", + serviceId: "quickbooks", +}); + +// Download attachment +const { data } = await apideck.accounting.attachments.download({ + referenceType: "invoice", + referenceId: "invoice_123", + id: "attachment_123", + serviceId: "quickbooks", +}); +``` + +## Company Info + +```typescript +const { data } = await apideck.accounting.companyInfo.get({ + serviceId: "quickbooks", +}); +// Returns: legal_name, company_name, currency, fiscal_year_start_month, addresses, phone_numbers +``` diff --git a/skills/apideck-node/references/ats-api.md b/skills/apideck-node/references/ats-api.md new file mode 100644 index 0000000..7647207 --- /dev/null +++ b/skills/apideck-node/references/ats-api.md @@ -0,0 +1,121 @@ +# ATS API Reference + +Base namespace: `apideck.ats` + +Supported connectors: Greenhouse, Lever, Workable, Recruitee, Bullhorn, SAP SuccessFactors, Teamtailor, and more. + +## Jobs + +Jobs are read-only in the unified model. + +```typescript +// List jobs +const { data } = await apideck.ats.jobs.list({ + serviceId: "greenhouse", + limit: 50, +}); + +// Get job details +const { data } = await apideck.ats.jobs.get({ + id: "job_123", + serviceId: "greenhouse", +}); +``` + +Key job fields: `id`, `title`, `description`, `status` (`draft` | `open` | `closed`), `department`, `branch`, `recruiters[]`, `hiring_managers[]`, `addresses[]`, `confidential`, `salary`, `tags[]`, `created_at`, `updated_at`, `closing_date`. + +## Applicants + +```typescript +// List applicants +const { data } = await apideck.ats.applicants.list({ + serviceId: "greenhouse", + limit: 50, +}); + +// Create applicant +const { data } = await apideck.ats.applicants.create({ + serviceId: "greenhouse", + applicant: { + first_name: "Sarah", + last_name: "Chen", + emails: [{ email: "sarah@example.com", type: "primary" }], + phone_numbers: [{ number: "+1234567890", type: "mobile" }], + title: "Senior Software Engineer", + websites: [ + { url: "https://github.com/sarahchen", type: "github" }, + { url: "https://linkedin.com/in/sarahchen", type: "linkedin" }, + ], + social_links: [ + { url: "https://twitter.com/sarahchen", type: "twitter" }, + ], + tags: ["referral", "senior"], + }, +}); + +// Get applicant +const { data } = await apideck.ats.applicants.get({ + id: "applicant_123", + serviceId: "greenhouse", +}); + +// Update applicant +const { data } = await apideck.ats.applicants.update({ + id: "applicant_123", + serviceId: "greenhouse", + applicant: { + tags: ["referral", "senior", "fast-track"], + }, +}); + +// Delete applicant +await apideck.ats.applicants.delete({ + id: "applicant_123", + serviceId: "greenhouse", +}); +``` + +Key applicant fields: `id`, `first_name`, `last_name`, `name`, `title`, `emails[]`, `phone_numbers[]`, `addresses[]`, `websites[]`, `social_links[]`, `stage_id`, `recruiter_id`, `coordinator_id`, `applications[]`, `tags[]`, `sources[]`, `confidential`, `custom_fields[]`. + +## Applications + +```typescript +// List applications +const { data } = await apideck.ats.applications.list({ + serviceId: "greenhouse", +}); + +// Create application (link applicant to job) +const { data } = await apideck.ats.applications.create({ + serviceId: "greenhouse", + application: { + applicant_id: "applicant_123", + job_id: "job_456", + stage_id: "stage_phone_screen", + status: "open", + }, +}); + +// Get application +const { data } = await apideck.ats.applications.get({ + id: "application_123", + serviceId: "greenhouse", +}); + +// Update application (advance stage) +const { data } = await apideck.ats.applications.update({ + id: "application_123", + serviceId: "greenhouse", + application: { + stage_id: "stage_onsite_interview", + }, +}); + +// Delete application +await apideck.ats.applications.delete({ + id: "application_123", + serviceId: "greenhouse", +}); +``` + +Key application fields: `id`, `applicant_id`, `job_id`, `status` (`open` | `rejected` | `hired`), `stage`, `current_stage`, `reject_reason`, `source`, `custom_fields[]`, `created_at`, `updated_at`. diff --git a/skills/apideck-node/references/crm-api.md b/skills/apideck-node/references/crm-api.md new file mode 100644 index 0000000..764b3aa --- /dev/null +++ b/skills/apideck-node/references/crm-api.md @@ -0,0 +1,273 @@ +# CRM API Reference + +Base namespace: `apideck.crm` + +Supported connectors: Salesforce, HubSpot, Pipedrive, Microsoft Dynamics 365, Zoho CRM, Close, Copper, Freshsales, and 15+ more. + +## Contacts + +```typescript +// List contacts with filtering and sorting +const { data } = await apideck.crm.contacts.list({ + serviceId: "salesforce", + limit: 50, + filter: { + name: "John", + email: "john@example.com", + phone_number: "+1234567890", + company_id: "company_123", + owner_id: "user_456", + first_name: "John", + last_name: "Doe", + }, + sort: { by: "updated_at", direction: "desc" }, +}); + +// Create contact +const { data } = await apideck.crm.contacts.create({ + serviceId: "salesforce", + contact: { + first_name: "John", + last_name: "Doe", + title: "VP of Engineering", + department: "Engineering", + company_id: "company_123", + emails: [ + { email: "john@example.com", type: "primary" }, + { email: "john.doe@personal.com", type: "secondary" }, + ], + phone_numbers: [ + { number: "+1234567890", type: "mobile" }, + ], + addresses: [ + { + type: "primary", + street_1: "123 Main St", + city: "San Francisco", + state: "CA", + postal_code: "94105", + country: "US", + }, + ], + social_links: [ + { url: "https://linkedin.com/in/johndoe", type: "linkedin" }, + ], + tags: ["vip", "enterprise"], + custom_fields: [ + { id: "lead_source", value: "Website" }, + ], + }, +}); + +// Get contact +const { data } = await apideck.crm.contacts.get({ + id: "contact_123", + serviceId: "salesforce", +}); + +// Update contact +const { data } = await apideck.crm.contacts.update({ + id: "contact_123", + serviceId: "salesforce", + contact: { title: "CTO" }, +}); + +// Delete contact +await apideck.crm.contacts.delete({ + id: "contact_123", + serviceId: "salesforce", +}); +``` + +Key contact fields: `id`, `name`, `first_name`, `last_name`, `title`, `department`, `company_id`, `company_name`, `emails[]`, `phone_numbers[]`, `addresses[]`, `social_links[]`, `tags[]`, `owner_id`, `lead_source`, `description`, `custom_fields[]`, `created_at`, `updated_at`. + +## Companies + +```typescript +// List companies +const { data } = await apideck.crm.companies.list({ + serviceId: "hubspot", + filter: { name: "Acme" }, + sort: { by: "name", direction: "asc" }, +}); + +// Create company +const { data } = await apideck.crm.companies.create({ + serviceId: "hubspot", + company: { + name: "Acme Corporation", + industry: "Technology", + website: "https://acme.com", + annual_revenue: "10000000", + number_of_employees: "500", + emails: [{ email: "info@acme.com", type: "primary" }], + phone_numbers: [{ number: "+1234567890", type: "primary" }], + addresses: [ + { + type: "primary", + street_1: "456 Tech Blvd", + city: "San Francisco", + state: "CA", + postal_code: "94105", + country: "US", + }, + ], + }, +}); +``` + +Key company fields: `id`, `name`, `industry`, `website`, `annual_revenue`, `number_of_employees`, `owner_id`, `emails[]`, `phone_numbers[]`, `addresses[]`, `social_links[]`, `tags[]`, `description`, `custom_fields[]`. + +## Leads + +```typescript +// List leads +const { data } = await apideck.crm.leads.list({ + serviceId: "salesforce", + filter: { email: "lead@example.com", name: "Jane" }, +}); + +// Create lead +const { data } = await apideck.crm.leads.create({ + serviceId: "salesforce", + lead: { + name: "Jane Smith", + first_name: "Jane", + last_name: "Smith", + company_name: "Startup Inc", + title: "CEO", + emails: [{ email: "jane@startup.com", type: "primary" }], + phone_numbers: [{ number: "+1987654321", type: "mobile" }], + lead_source: "Webinar", + monetary_amount: 50000, + currency: "USD", + }, +}); + +// Convert lead (connector-specific, use pass-through) +const { data } = await apideck.crm.leads.update({ + id: "lead_123", + serviceId: "salesforce", + lead: { + pass_through: [ + { + service_id: "salesforce", + extend_object: { Status: "Converted" }, + }, + ], + }, +}); +``` + +## Opportunities + +```typescript +// List opportunities +const { data } = await apideck.crm.opportunities.list({ + serviceId: "pipedrive", + filter: { status: "open" }, +}); + +// Create opportunity +const { data } = await apideck.crm.opportunities.create({ + serviceId: "pipedrive", + opportunity: { + title: "Enterprise deal - Acme Corp", + primary_contact_id: "contact_123", + company_id: "company_456", + pipeline_id: "pipeline_789", + stage_id: "stage_negotiation", + status: "open", + monetary_amount: 150000, + currency: "USD", + win_probability: 60, + close_date: "2024-09-30", + owner_id: "user_123", + }, +}); + +// Update opportunity stage +const { data } = await apideck.crm.opportunities.update({ + id: "opportunity_123", + serviceId: "pipedrive", + opportunity: { + stage_id: "stage_closed_won", + status: "won", + win_probability: 100, + }, +}); +``` + +Key opportunity fields: `id`, `title`, `primary_contact_id`, `company_id`, `pipeline_id`, `stage_id`, `status` (`open` | `won` | `lost`), `monetary_amount`, `currency`, `win_probability`, `close_date`, `owner_id`, `priority`, `tags[]`. + +## Activities + +```typescript +// List activities +const { data } = await apideck.crm.activities.list({ + serviceId: "salesforce", +}); + +// Create activity (call, meeting, email, task, etc.) +const { data } = await apideck.crm.activities.create({ + serviceId: "salesforce", + activity: { + type: "call", + title: "Follow-up call with Acme", + description: "Discuss contract renewal", + activity_date: "2024-07-01T10:00:00.000Z", + duration_seconds: 1800, + owner_id: "user_123", + contact_id: "contact_456", + company_id: "company_789", + }, +}); +``` + +Activity types: `call`, `meeting`, `email`, `note`, `task`, `deadline`, `send_letter`, `send_quote`, `other`. + +## Notes + +```typescript +// List notes +const { data } = await apideck.crm.notes.list({ + serviceId: "hubspot", +}); + +// Create note +const { data } = await apideck.crm.notes.create({ + serviceId: "hubspot", + note: { + title: "Meeting summary", + content: "Discussed Q3 renewal. Client interested in premium tier.", + contact_id: "contact_123", + company_id: "company_456", + opportunity_id: "opportunity_789", + }, +}); +``` + +## Pipelines + +```typescript +// List pipelines +const { data } = await apideck.crm.pipelines.list({ + serviceId: "pipedrive", +}); +// Returns: id, name, stages[{ id, name, display_order }], currency + +// Get pipeline with stages +const { data } = await apideck.crm.pipelines.get({ + id: "pipeline_123", + serviceId: "pipedrive", +}); +``` + +## Users + +```typescript +const { data } = await apideck.crm.users.list({ + serviceId: "salesforce", +}); +// Returns CRM users: id, name, email, role, status +``` diff --git a/skills/apideck-node/references/file-storage-api.md b/skills/apideck-node/references/file-storage-api.md new file mode 100644 index 0000000..5bdd059 --- /dev/null +++ b/skills/apideck-node/references/file-storage-api.md @@ -0,0 +1,196 @@ +# File Storage API Reference + +Base namespace: `apideck.fileStorage` + +Supported connectors: Box, Dropbox, Google Drive, OneDrive, SharePoint. + +## Files + +```typescript +// List files in a folder +const { data } = await apideck.fileStorage.files.list({ + serviceId: "google-drive", + filter: { + folder_id: "folder_123", + drive_id: "drive_456", + }, + sort: { by: "updated_at", direction: "desc" }, +}); + +// Search files +const { data } = await apideck.fileStorage.files.search({ + serviceId: "google-drive", + filesSearch: { + query: "quarterly report", + }, +}); + +// Get file metadata +const { data } = await apideck.fileStorage.files.get({ + id: "file_123", + serviceId: "google-drive", +}); + +// Upload a file +const { data } = await apideck.fileStorage.files.create({ + serviceId: "google-drive", + createFileRequest: { + name: "report.pdf", + parent_folder_id: "folder_123", + drive_id: "drive_456", + description: "Q3 financial report", + }, +}); + +// Download a file +const { data } = await apideck.fileStorage.files.download({ + id: "file_123", + serviceId: "google-drive", +}); + +// Update file metadata +const { data } = await apideck.fileStorage.files.update({ + id: "file_123", + serviceId: "google-drive", + updateFileRequest: { + name: "report-v2.pdf", + description: "Updated Q3 report", + }, +}); + +// Delete file +await apideck.fileStorage.files.delete({ + id: "file_123", + serviceId: "google-drive", +}); +``` + +Key file fields: `id`, `name`, `description`, `mime_type`, `size`, `parent_folders[]`, `owner`, `permissions[]`, `downloadable`, `exportable`, `created_at`, `updated_at`. + +## Folders + +```typescript +// Get folder contents +const { data } = await apideck.fileStorage.folders.get({ + id: "folder_123", + serviceId: "dropbox", +}); + +// Create folder +const { data } = await apideck.fileStorage.folders.create({ + serviceId: "dropbox", + createFolderRequest: { + name: "Project Documents", + parent_folder_id: "folder_root", + drive_id: "drive_123", + }, +}); + +// Update folder +const { data } = await apideck.fileStorage.folders.update({ + id: "folder_123", + serviceId: "dropbox", + updateFolderRequest: { + name: "Project Docs - Archived", + }, +}); + +// Copy folder +const { data } = await apideck.fileStorage.folders.copy({ + id: "folder_123", + serviceId: "dropbox", + copyFolderRequest: { + parent_folder_id: "folder_destination", + name: "Project Documents (Copy)", + }, +}); + +// Delete folder +await apideck.fileStorage.folders.delete({ + id: "folder_123", + serviceId: "dropbox", +}); +``` + +## Shared Links + +```typescript +// List shared links +const { data } = await apideck.fileStorage.sharedLinks.list({ + serviceId: "box", +}); + +// Create shared link +const { data } = await apideck.fileStorage.sharedLinks.create({ + serviceId: "box", + sharedLink: { + target: { id: "file_123", type: "file" }, + scope: "company", + password: "secure-password", + expires_at: "2024-12-31T23:59:59.000Z", + }, +}); + +// Delete shared link +await apideck.fileStorage.sharedLinks.delete({ + id: "link_123", + serviceId: "box", +}); +``` + +Shared link scopes: `public`, `company`, `password`. + +## Upload Sessions (large files) + +For files larger than the direct upload limit, use chunked upload sessions: + +```typescript +// 1. Create upload session +const { data: session } = await apideck.fileStorage.uploadSessions.create({ + serviceId: "box", + createUploadSessionRequest: { + name: "large-backup.zip", + parent_folder_id: "folder_123", + size: 104857600, // 100MB in bytes + }, +}); + +// 2. Upload parts (SDK handles chunking) +await apideck.fileStorage.uploadSessions.upload({ + id: session.id, + serviceId: "box", + // file part data +}); + +// 3. Finish upload session +const { data: file } = await apideck.fileStorage.uploadSessions.finish({ + id: session.id, + serviceId: "box", +}); +``` + +## Drives + +```typescript +// List drives +const { data } = await apideck.fileStorage.drives.list({ + serviceId: "google-drive", +}); + +// Create drive (shared drives) +const { data } = await apideck.fileStorage.drives.create({ + serviceId: "google-drive", + drive: { + name: "Engineering Shared Drive", + description: "Shared documents for the engineering team", + }, +}); +``` + +## Drive Groups (SharePoint only) + +```typescript +const { data } = await apideck.fileStorage.driveGroups.list({ + serviceId: "sharepoint", +}); +``` diff --git a/skills/apideck-node/references/hris-api.md b/skills/apideck-node/references/hris-api.md new file mode 100644 index 0000000..0b5d1e6 --- /dev/null +++ b/skills/apideck-node/references/hris-api.md @@ -0,0 +1,212 @@ +# HRIS API Reference + +Base namespace: `apideck.hris` + +Supported connectors: Workday, BambooHR, Hibob, SAP SuccessFactors, Personio, Gusto, Rippling, Deel, ADP, Namely, Payfit, and 40+ more. + +## Employees + +```typescript +// List employees +const { data } = await apideck.hris.employees.list({ + serviceId: "bamboohr", + limit: 50, + filter: { + company_id: "company_123", + department_id: "dept_456", + employment_status: "active", + manager_id: "emp_789", + title: "Engineer", + }, + sort: { by: "last_name", direction: "asc" }, +}); + +// Create employee +const { data } = await apideck.hris.employees.create({ + serviceId: "bamboohr", + employee: { + first_name: "Alice", + last_name: "Johnson", + display_name: "Alice Johnson", + gender: "female", + birthday: "1990-05-15", + emails: [{ email: "alice@company.com", type: "work" }], + phone_numbers: [{ number: "+1234567890", type: "mobile" }], + addresses: [ + { + type: "primary", + street_1: "789 Oak Ave", + city: "Austin", + state: "TX", + postal_code: "73301", + country: "US", + }, + ], + employment_status: "active", + employment_role: { type: "employee", sub_type: "full_time" }, + department_id: "dept_engineering", + title: "Senior Software Engineer", + manager: { id: "emp_manager_123" }, + start_date: "2024-03-01", + compensations: [ + { + rate: 150000, + payment_unit: "year", + currency: "USD", + effective_date: "2024-03-01", + }, + ], + }, +}); + +// Get employee +const { data } = await apideck.hris.employees.get({ + id: "emp_123", + serviceId: "bamboohr", +}); + +// Update employee +const { data } = await apideck.hris.employees.update({ + id: "emp_123", + serviceId: "bamboohr", + employee: { + title: "Staff Software Engineer", + compensations: [ + { + rate: 175000, + payment_unit: "year", + currency: "USD", + effective_date: "2025-01-01", + }, + ], + }, +}); + +// Delete employee +await apideck.hris.employees.delete({ + id: "emp_123", + serviceId: "bamboohr", +}); +``` + +Key employee fields: `id`, `first_name`, `last_name`, `display_name`, `title`, `department_id`, `department`, `company_id`, `company_name`, `employment_status` (`active` | `inactive` | `terminated`), `employment_role`, `manager`, `start_date`, `termination_date`, `birthday`, `gender`, `emails[]`, `phone_numbers[]`, `addresses[]`, `jobs[]`, `compensations[]`, `teams[]`, `social_links[]`, `custom_fields[]`. + +Employment role types: `employee`, `contractor`. Sub-types: `full_time`, `part_time`, `intern`, `freelance`, `temp`, `seasonal`. + +## Departments + +```typescript +// List departments +const { data } = await apideck.hris.departments.list({ + serviceId: "bamboohr", +}); + +// Create department +const { data } = await apideck.hris.departments.create({ + serviceId: "bamboohr", + department: { + name: "Product Engineering", + code: "PROD-ENG", + parent_id: "dept_engineering", + }, +}); +``` + +## Companies + +```typescript +// List HRIS companies (for multi-entity organizations) +const { data } = await apideck.hris.companies.list({ + serviceId: "bamboohr", +}); + +// Get company details +const { data } = await apideck.hris.companies.get({ + id: "company_123", + serviceId: "bamboohr", +}); +// Returns: legal_name, display_name, status, addresses, phone_numbers, ein (tax ID) +``` + +## Payrolls + +```typescript +// List payrolls +const { data } = await apideck.hris.payrolls.list({ + serviceId: "gusto", + filter: { + start_date: "2024-01-01", + end_date: "2024-01-31", + }, +}); + +// Get specific payroll +const { data } = await apideck.hris.payrolls.get({ + payrollId: "payroll_123", + serviceId: "gusto", +}); +// Returns: id, processed_date, check_date, totals (gross_pay, net_pay, total_tax, total_deductions) +``` + +## Employee Payrolls + +```typescript +// List employee payrolls for a specific employee +const { data } = await apideck.hris.employeePayrolls.list({ + employeeId: "emp_123", + serviceId: "gusto", +}); +// Returns per-employee: gross_pay, net_pay, taxes[], deductions[], compensations[] +``` + +## Time-Off Requests + +```typescript +// List time-off requests +const { data } = await apideck.hris.timeOffRequests.list({ + serviceId: "bamboohr", + filter: { + employee_id: "emp_123", + start_date: "2024-06-01", + end_date: "2024-06-30", + }, +}); + +// Create time-off request +const { data } = await apideck.hris.timeOffRequests.create({ + serviceId: "bamboohr", + timeOffRequest: { + employee_id: "emp_123", + policy_id: "policy_pto", + start_date: "2024-07-15", + end_date: "2024-07-19", + status: "requested", + request_type: "vacation", + notes: { employee: "Family vacation" }, + }, +}); + +// Update time-off request (approve/deny) +const { data } = await apideck.hris.timeOffRequests.update({ + id: "time_off_123", + serviceId: "bamboohr", + timeOffRequest: { + status: "approved", + notes: { manager: "Approved. Enjoy!" }, + }, +}); +``` + +Time-off statuses: `requested`, `approved`, `declined`, `cancelled`, `deleted`. +Request types: `vacation`, `sick`, `personal`, `jury_duty`, `volunteer`, `bereavement`. + +## Employee Schedules + +```typescript +// List employee schedules +const { data } = await apideck.hris.employeeSchedules.list({ + employeeId: "emp_123", + serviceId: "bamboohr", +}); +// Returns weekly schedule patterns with work days and hours +``` diff --git a/skills/apideck-node/references/vault-api.md b/skills/apideck-node/references/vault-api.md new file mode 100644 index 0000000..2969ac9 --- /dev/null +++ b/skills/apideck-node/references/vault-api.md @@ -0,0 +1,164 @@ +# Vault API Reference + +Base namespace: `apideck.vault` + +Vault manages authentication and connections between your application and downstream services. It provides embeddable UI components for end-users to authorize their own integrations. + +## Connections + +```typescript +// List all connections for a consumer +const { data } = await apideck.vault.connections.list({ + api: "crm", +}); +// Returns: id, service_id, name, state, auth_type, enabled, created_at + +// Get connection details +const { data } = await apideck.vault.connections.get({ + serviceId: "salesforce", + unifiedApi: "crm", +}); + +// Update connection settings +const { data } = await apideck.vault.connections.update({ + serviceId: "salesforce", + unifiedApi: "crm", + connection: { + enabled: true, + settings: { + instance_url: "https://mycompany.salesforce.com", + }, + }, +}); + +// Delete connection +await apideck.vault.connections.delete({ + serviceId: "salesforce", + unifiedApi: "crm", +}); + +// Import connection (bring existing OAuth tokens) +const { data } = await apideck.vault.connections.imports({ + serviceId: "salesforce", + unifiedApi: "crm", + connectionImportData: { + credentials: { + access_token: "existing-access-token", + refresh_token: "existing-refresh-token", + }, + }, +}); +``` + +Connection states: `available`, `callable`, `added`, `authorized`, `invalid`. + +## Connection Settings + +```typescript +// Get available settings for a connection +const { data } = await apideck.vault.connectionSettings.list({ + unifiedApi: "accounting", + serviceId: "quickbooks", + resource: "invoices", +}); +// Returns configurable fields, allowed values, and current settings +``` + +## Sessions + +Create a Vault session URL to embed the connection management UI: + +```typescript +// Create session for Vault embedded UI +const { data } = await apideck.vault.sessions.create({ + session: { + consumer_metadata: { + account_name: "Acme Corp", + user_name: "John Doe", + email: "john@acme.com", + image: "https://acme.com/john.jpg", + }, + redirect_uri: "https://myapp.com/integrations", + settings: { + unified_apis: ["accounting", "crm"], + hide_resource_settings: false, + sandbox_mode: false, + auto_redirect: false, + }, + theme: { + vault_name: "My App Integrations", + primary_color: "#4F46E5", + sidepanel_background_color: "#F9FAFB", + sidepanel_text_color: "#111827", + favicon: "https://myapp.com/favicon.ico", + logo: "https://myapp.com/logo.png", + }, + }, +}); +// data.session_uri -> redirect user here to manage connections +``` + +## Consumers + +```typescript +// List consumers +const { data } = await apideck.vault.consumers.list({ limit: 50 }); + +// Create consumer +const { data } = await apideck.vault.consumers.create({ + consumer: { + consumer_id: "user_abc123", + metadata: { + account_name: "Acme Corp", + user_name: "John Doe", + email: "john@acme.com", + }, + }, +}); + +// Get consumer +const { data } = await apideck.vault.consumers.get({ + consumerId: "user_abc123", +}); + +// Delete consumer and all their connections +await apideck.vault.consumers.delete({ + consumerId: "user_abc123", +}); +``` + +## Custom Mappings + +Map connector-specific fields to your unified model: + +```typescript +// List custom mappings for a connection +const { data } = await apideck.vault.customMappings.list({ + unifiedApi: "crm", + serviceId: "salesforce", +}); + +// Update custom field mapping +const { data } = await apideck.vault.customMappings.update({ + unifiedApi: "crm", + serviceId: "salesforce", + id: "mapping_123", + customMapping: { + value: "$.custom_sf_field__c", + }, +}); +``` + +## Logs + +```typescript +// List API call logs +const { data } = await apideck.vault.logs.list({ + filter: { + connector_id: "salesforce", + status_code: 200, + exclude_unified_apis: "vault,proxy", + }, +}); +// Returns: api_style, base_url, path, method, status_code, duration, consumer_id, service_id, timestamp +``` diff --git a/skills/apideck-node/references/vault-js.md b/skills/apideck-node/references/vault-js.md new file mode 100644 index 0000000..eb7e455 --- /dev/null +++ b/skills/apideck-node/references/vault-js.md @@ -0,0 +1,293 @@ +# Vault JS Integration Reference + +Vault JS is a vanilla JavaScript library to embed Apideck Vault in any web application. It lets your users authorize connectors and manage integration settings through a pre-built modal UI, and stores credentials securely so you can make authorized API calls on their behalf. + +## Installation + +```bash +npm install @apideck/vault-js +``` + +Or load via CDN (available globally as `window.ApideckVault`): + +```html + +``` + +## Prerequisites + +Session creation MUST happen server-side to prevent token leakage. Create a session using the `@apideck/unify` SDK before opening Vault: + +```typescript +import { Apideck } from "@apideck/unify"; + +const apideck = new Apideck({ + apiKey: process.env["APIDECK_API_KEY"] ?? "", + appId: "your-app-id", + consumerId: "user_abc123", +}); + +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"], + hide_resource_settings: false, + sandbox_mode: false, + }, + theme: { + vault_name: "My App Integrations", + primary_color: "#4F46E5", + sidepanel_background_color: "#F9FAFB", + sidepanel_text_color: "#111827", + favicon: "https://myapp.com/favicon.ico", + logo: "https://myapp.com/logo.png", + }, + }, +}); + +const sessionToken = data.session_token; +// Pass this token to your frontend +``` + +## Opening Vault + +```typescript +import { ApideckVault } from "@apideck/vault-js"; + +// Basic usage — open Vault modal with a session token +ApideckVault.open({ + token: "SESSION_TOKEN_FROM_BACKEND", +}); +``` + +## Configuration Options + +```typescript +ApideckVault.open({ + // REQUIRED: JWT session token from your backend + token: "SESSION_TOKEN_FROM_BACKEND", + + // Show integrations for a single Unified API only + unifiedApi: "accounting", + + // Open Vault for a single integration only + serviceId: "quickbooks", + + // Initial view to display + // "settings" | "configurable-resources" | "custom-mapping" + initialView: "settings", + + // Locale for the modal UI + // "en" (default) | "nl" | "de" | "fr" | "es" + locale: "en", + + // Show language switch in the modal + showLanguageSwitch: true, + + // Use button layout instead of dropdown menus + showButtonLayout: false, + + // Automatically start OAuth flow when opening a connection + autoStartAuthorization: true, + + // Callback when modal is ready and visible + onReady: () => { + console.log("Vault modal is open"); + }, + + // Callback when modal is closed + onClose: () => { + console.log("Vault modal closed"); + }, + + // Callback when a connection is added, updated, or authorized + onConnectionChange: (connection) => { + console.log("Connection changed:", connection); + // connection: { id, service_id, unified_api, state, ... } + }, + + // Callback when a connection is deleted + onConnectionDelete: (connection) => { + console.log("Connection deleted:", connection); + }, +}); +``` + +## Closing Vault Programmatically + +```typescript +ApideckVault.close(); +``` + +## Full Integration Example + +### Server-side (Node.js / Express) + +```typescript +import express from "express"; +import { Apideck } from "@apideck/unify"; + +const app = express(); + +const apideck = new Apideck({ + apiKey: process.env["APIDECK_API_KEY"] ?? "", + appId: process.env["APIDECK_APP_ID"] ?? "", + consumerId: "", // set per-request +}); + +// Create a Vault session for the authenticated user +app.post("/api/vault/session", async (req, res) => { + const userId = req.user.id; // from your auth middleware + + const client = new Apideck({ + apiKey: process.env["APIDECK_API_KEY"] ?? "", + appId: process.env["APIDECK_APP_ID"] ?? "", + consumerId: userId, + }); + + const { data } = await client.vault.sessions.create({ + session: { + consumer_metadata: { + account_name: req.user.company_name, + user_name: req.user.name, + email: req.user.email, + }, + redirect_uri: `${process.env["APP_URL"]}/integrations`, + settings: { + unified_apis: ["accounting", "crm", "hris"], + }, + theme: { + vault_name: "My App", + primary_color: "#4F46E5", + }, + }, + }); + + res.json({ token: data.session_token }); +}); +``` + +### Client-side (Vanilla JS) + +```typescript +import { ApideckVault } from "@apideck/vault-js"; + +async function openVault() { + // Fetch session token from your backend + const response = await fetch("/api/vault/session", { method: "POST" }); + const { token } = await response.json(); + + ApideckVault.open({ + token, + onReady: () => console.log("Vault opened"), + onClose: () => console.log("Vault closed"), + onConnectionChange: (connection) => { + console.log(`${connection.service_id} is now ${connection.state}`); + // Refresh your integrations list + loadIntegrations(); + }, + onConnectionDelete: (connection) => { + console.log(`${connection.service_id} disconnected`); + loadIntegrations(); + }, + }); +} + +document.getElementById("manage-integrations") + ?.addEventListener("click", openVault); +``` + +### Client-side (React) + +```tsx +import { ApideckVault } from "@apideck/vault-js"; +import { useCallback, useState } from "react"; + +function IntegrationsButton() { + const [isLoading, setIsLoading] = useState(false); + + const handleOpenVault = useCallback(async () => { + setIsLoading(true); + try { + const response = await fetch("/api/vault/session", { method: "POST" }); + const { token } = await response.json(); + + ApideckVault.open({ + token, + onReady: () => setIsLoading(false), + onClose: () => console.log("Vault closed"), + onConnectionChange: (connection) => { + console.log("Connection changed:", connection); + }, + }); + } catch (error) { + console.error("Failed to create Vault session:", error); + setIsLoading(false); + } + }, []); + + return ( + + ); +} +``` + +## Filtering by API or Connector + +```typescript +// Show only accounting integrations +ApideckVault.open({ + token: "SESSION_TOKEN", + unifiedApi: "accounting", +}); + +// Open directly to QuickBooks settings +ApideckVault.open({ + token: "SESSION_TOKEN", + serviceId: "quickbooks", + initialView: "settings", +}); + +// Open custom field mapping for Salesforce +ApideckVault.open({ + token: "SESSION_TOKEN", + serviceId: "salesforce", + initialView: "custom-mapping", +}); +``` + +## Theming + +Theme customization is set during session creation on the server side: + +```typescript +const { data } = await apideck.vault.sessions.create({ + session: { + theme: { + vault_name: "My App Integrations", + primary_color: "#4F46E5", + sidepanel_background_color: "#F9FAFB", + sidepanel_text_color: "#111827", + favicon: "https://myapp.com/favicon.ico", + logo: "https://myapp.com/logo.png", + }, + }, +}); +``` + +| Theme Property | Description | +|----------------|-------------| +| `vault_name` | Display name shown in the modal header | +| `primary_color` | Primary accent color (hex) | +| `sidepanel_background_color` | Side panel background (hex) | +| `sidepanel_text_color` | Side panel text color (hex) | +| `favicon` | URL to your favicon | +| `logo` | URL to your logo image | diff --git a/skills/apideck-node/references/webhook-api.md b/skills/apideck-node/references/webhook-api.md new file mode 100644 index 0000000..457e579 --- /dev/null +++ b/skills/apideck-node/references/webhook-api.md @@ -0,0 +1,162 @@ +# Webhook API Reference + +Base namespace: `apideck.webhook` + +Apideck supports both **native webhooks** (from connectors that support them natively) and **virtual webhooks** (Apideck polls the connector and delivers events to your webhook URL). + +## Webhook Subscriptions + +```typescript +// List webhooks +const { data } = await apideck.webhook.webhooks.list(); + +// Create webhook subscription +const { data } = await apideck.webhook.webhooks.create({ + webhook: { + description: "CRM contact changes", + unified_api: "crm", + status: "enabled", + delivery_url: "https://myapp.com/webhooks/apideck", + events: [ + "crm.contact.created", + "crm.contact.updated", + "crm.contact.deleted", + ], + }, +}); + +// Get webhook +const { data } = await apideck.webhook.webhooks.get({ + id: "webhook_123", +}); + +// Update webhook +const { data } = await apideck.webhook.webhooks.update({ + id: "webhook_123", + webhook: { + description: "All CRM changes", + events: [ + "crm.contact.created", + "crm.contact.updated", + "crm.contact.deleted", + "crm.company.created", + "crm.company.updated", + "crm.lead.created", + ], + }, +}); + +// Delete webhook +await apideck.webhook.webhooks.delete({ + id: "webhook_123", +}); +``` + +## Webhook Event Types + +Events follow the pattern `{unified_api}.{resource}.{action}`: + +### Accounting +- `accounting.invoice.created` / `.updated` / `.deleted` +- `accounting.bill.created` / `.updated` / `.deleted` +- `accounting.payment.created` / `.updated` / `.deleted` +- `accounting.customer.created` / `.updated` / `.deleted` +- `accounting.supplier.created` / `.updated` / `.deleted` +- `accounting.ledger-account.created` / `.updated` / `.deleted` +- `accounting.journal-entry.created` / `.updated` / `.deleted` +- `accounting.credit-note.created` / `.updated` / `.deleted` +- `accounting.expense.created` / `.updated` / `.deleted` + +### CRM +- `crm.contact.created` / `.updated` / `.deleted` +- `crm.company.created` / `.updated` / `.deleted` +- `crm.lead.created` / `.updated` / `.deleted` +- `crm.opportunity.created` / `.updated` / `.deleted` +- `crm.activity.created` / `.updated` / `.deleted` +- `crm.note.created` / `.updated` / `.deleted` + +### HRIS +- `hris.employee.created` / `.updated` / `.deleted` / `.terminated` +- `hris.department.created` / `.updated` / `.deleted` +- `hris.company.created` / `.updated` / `.deleted` + +### File Storage +- `file-storage.file.created` / `.updated` / `.deleted` +- `file-storage.folder.created` / `.updated` / `.deleted` + +### ATS +- `ats.applicant.created` / `.updated` / `.deleted` +- `ats.application.created` / `.updated` / `.deleted` +- `ats.job.created` / `.updated` / `.deleted` + +## Webhook Payload Format + +```json +{ + "payload": { + "event_type": "crm.contact.updated", + "unified_api": "crm", + "service_id": "salesforce", + "consumer_id": "user_abc123", + "entity_id": "contact_123", + "entity_type": "contact", + "entity_url": "https://unify.apideck.com/crm/contacts/contact_123", + "occurred_at": "2024-06-15T10:30:00.000Z" + } +} +``` + +## Webhook Signature Verification + +Verify webhook signatures to ensure requests are from Apideck: + +```typescript +import crypto from "crypto"; + +function verifyWebhookSignature( + payload: string, + signature: string, + secret: string +): boolean { + const hmac = crypto.createHmac("sha256", secret); + hmac.update(payload); + const expectedSignature = hmac.digest("base64"); + return crypto.timingSafeEqual( + Buffer.from(signature), + Buffer.from(expectedSignature) + ); +} + +// In your webhook handler +app.post("/webhooks/apideck", (req, res) => { + const signature = req.headers["x-apideck-signature"] as string; + const isValid = verifyWebhookSignature( + JSON.stringify(req.body), + signature, + process.env.APIDECK_WEBHOOK_SECRET! + ); + if (!isValid) { + return res.status(401).send("Invalid signature"); + } + // Process event + const { event_type, entity_id, service_id } = req.body.payload; + console.log(`Received ${event_type} for ${entity_id} from ${service_id}`); + res.status(200).send("OK"); +}); +``` + +## Event Logs + +```typescript +// List webhook event logs +const { data } = await apideck.webhook.eventLogs.list({ + filter: { + exclude_apis: "vault,proxy", + service: { id: "salesforce" }, + consumer_id: "user_abc123", + entity_type: "contact", + event_type: "crm.contact.updated", + }, +}); +// Returns: event delivery status, attempts, request/response details +``` diff --git a/skills/apideck-php/SKILL.md b/skills/apideck-php/SKILL.md new file mode 100644 index 0000000..d332489 --- /dev/null +++ b/skills/apideck-php/SKILL.md @@ -0,0 +1,213 @@ +--- +name: apideck-php +description: Apideck Unified API integration patterns for PHP. 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 using PHP. Covers the apideck-libraries/sdk-php Composer package, authentication, CRUD operations, pagination, error handling, and Vault connection management. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +# Apideck PHP SDK Skill + +## Overview + +The [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 PHP SDK provides typed clients for all unified APIs. + +## Installation + +```sh +composer require apideck-libraries/sdk-php +``` + +## IMPORTANT RULES + +- ALWAYS use the `apideck-libraries/sdk-php` Composer package. DO NOT make raw `Guzzle`/`curl` calls to the Apideck API. +- ALWAYS pass security, app ID, and consumer ID via the builder when creating the client. +- USE `serviceId` on requests to specify which downstream connector to use. +- ALWAYS handle errors with try/catch using `Errors\APIException` as the base class. +- DO NOT store API keys in source code. Use environment variables. + +## Quick Start + +```php +setConsumerId('your-consumer-id') + ->setAppId('your-app-id') + ->setSecurity(getenv('APIDECK_API_KEY')) + ->build(); + +$request = new Operations\CrmContactsAllRequest( + serviceId: 'salesforce', + limit: 20, +); + +$responses = $sdk->crm->contacts->list(request: $request); + +foreach ($responses as $response) { + if ($response->httpMeta->response->getStatusCode() === 200) { + foreach ($response->getContactsResponse->data as $contact) { + echo $contact->name . "\n"; + } + } +} +``` + +## SDK Patterns + +### Client Setup + +```php +use Apideck\Unify; + +$sdk = Unify\Apideck::builder() + ->setConsumerId('your-consumer-id') + ->setAppId('your-app-id') + ->setSecurity(getenv('APIDECK_API_KEY')) + ->build(); +``` + +### CRUD Operations + +All resources follow the pattern: `$sdk->{api}->{resource}->{operation}(request: $request)`. + +```php +use Apideck\Unify\Models\Operations; +use Apideck\Unify\Models\Components; + +// LIST +$request = new Operations\CrmContactsAllRequest( + serviceId: 'salesforce', + limit: 20, + filter: new Components\ContactsFilter(email: 'john@example.com'), + sort: new Components\ContactsSort( + by: Components\ContactsSortBy::UpdatedAt, + direction: Components\SortDirection::Desc, + ), +); +$responses = $sdk->crm->contacts->list(request: $request); + +// CREATE +$request = new Operations\CrmContactsAddRequest( + serviceId: 'salesforce', + contact: new Components\ContactInput( + firstName: 'John', + lastName: 'Doe', + emails: [ + new Components\Email(email: 'john@example.com', type: Components\EmailType::Primary), + ], + phoneNumbers: [ + new Components\PhoneNumber(number: '+1234567890', type: Components\PhoneNumberType::Mobile), + ], + ), +); +$response = $sdk->crm->contacts->create(request: $request); + +// GET +$request = new Operations\CrmContactsOneRequest( + id: 'contact_123', + serviceId: 'salesforce', +); +$response = $sdk->crm->contacts->get(request: $request); + +// UPDATE +$request = new Operations\CrmContactsUpdateRequest( + id: 'contact_123', + serviceId: 'salesforce', + contact: new Components\ContactInput(firstName: 'Jane'), +); +$response = $sdk->crm->contacts->update(request: $request); + +// DELETE +$request = new Operations\CrmContactsDeleteRequest( + id: 'contact_123', + serviceId: 'salesforce', +); +$response = $sdk->crm->contacts->delete(request: $request); +``` + +### Pagination + +The `list` method returns a PHP Generator. Iterate with `foreach`: + +```php +$request = new Operations\AccountingInvoicesAllRequest( + serviceId: 'quickbooks', + limit: 50, +); + +$responses = $sdk->accounting->invoices->list(request: $request); + +foreach ($responses as $response) { + if ($response->httpMeta->response->getStatusCode() === 200) { + foreach ($response->getInvoicesResponse->data as $invoice) { + echo "{$invoice->number}: {$invoice->total}\n"; + } + } +} +``` + +### Error Handling + +```php +use Apideck\Unify\Models\Errors; + +try { + $responses = $sdk->crm->contacts->list(request: $request); + foreach ($responses as $response) { + // handle response + } +} catch (Errors\BadRequestResponseThrowable $e) { + echo "Bad request: " . $e->getMessage() . "\n"; +} catch (Errors\UnauthorizedResponseThrowable $e) { + echo "Unauthorized\n"; +} catch (Errors\NotFoundResponseThrowable $e) { + echo "Not found\n"; +} catch (Errors\PaymentRequiredResponseThrowable $e) { + echo "API limit reached\n"; +} catch (Errors\UnprocessableResponseThrowable $e) { + echo "Validation error: " . $e->getMessage() . "\n"; +} catch (Errors\APIException $e) { + echo "API error: " . $e->getMessage() . "\n"; +} +``` + +### Retry Configuration + +```php +use Apideck\Unify\Utils\Retry; + +// Global +$sdk = Unify\Apideck::builder() + ->setRetryConfig( + new Retry\RetryConfigBackoff( + initialInterval: 1, + maxInterval: 50, + exponent: 1.1, + maxElapsedTime: 100, + retryConnectionErrors: false, + ) + ) + ->setConsumerId('your-consumer-id') + ->setAppId('your-app-id') + ->setSecurity(getenv('APIDECK_API_KEY')) + ->build(); +``` + +## API Namespaces + +| Namespace | Resources | +|-----------|-----------| +| `$sdk->accounting->*` | invoices, bills, payments, customers, suppliers, ledgerAccounts, journalEntries, taxRates, creditNotes, purchaseOrders, balanceSheet, profitAndLoss, and more | +| `$sdk->crm->*` | contacts, companies, leads, opportunities, activities, notes, pipelines, users | +| `$sdk->hris->*` | employees, companies, departments, payrolls, timeOffRequests | +| `$sdk->fileStorage->*` | files, folders, drives, driveGroups, sharedLinks, uploadSessions | +| `$sdk->ats->*` | applicants, applications, jobs | +| `$sdk->vault->*` | connections, consumers, sessions, customMappings, logs | +| `$sdk->webhook->*` | webhooks, eventLogs | diff --git a/skills/apideck-php/metadata.json b/skills/apideck-php/metadata.json new file mode 100644 index 0000000..eccff68 --- /dev/null +++ b/skills/apideck-php/metadata.json @@ -0,0 +1 @@ +{"version":"1.0.0","organization":"Apideck","date":"February 2026","abstract":"PHP SDK patterns for all Apideck Unified APIs.","references":["https://developers.apideck.com","https://packagist.org/packages/apideck-libraries/sdk-php"]} diff --git a/skills/apideck-portman/SKILL.md b/skills/apideck-portman/SKILL.md new file mode 100644 index 0000000..a549727 --- /dev/null +++ b/skills/apideck-portman/SKILL.md @@ -0,0 +1,384 @@ +--- +name: apideck-portman +description: API contract testing with Portman by Apideck. Use when generating Postman collections from OpenAPI specs, writing contract tests, variation tests, integration tests, fuzz testing, or setting up CI/CD API test pipelines. Portman converts OpenAPI 3.x specs into Postman collections with auto-generated test suites. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +# Portman API Testing Skill + +## Overview + +[Portman](https://github.com/apideck-libraries/portman) converts OpenAPI 3.x specifications into Postman collections with auto-generated contract tests, variation tests, content tests, and integration tests. It runs tests via Newman (Postman's CLI runner) and integrates into CI/CD pipelines. + +## Installation + +```sh +npm install -g @apideck/portman +``` + +Or use without installing: + +```sh +npx @apideck/portman -l your-openapi-spec.yaml +``` + +## IMPORTANT RULES + +- ALWAYS use a `portman-config.json` (or `.yaml`) for test configuration. Do not rely solely on defaults for production use. +- ALWAYS target operations using `openApiOperationId` or `openApiOperation` (method::path) syntax. +- USE `--baseUrl` to override the spec's server URL when testing against local/staging environments. +- USE `--envFile` to inject environment variables. Variables prefixed with `PORTMAN_` auto-map to Postman collection variables. +- USE `assignVariables` to chain request/response values across operations (e.g., capture `id` from create, use in get/update/delete). +- DO NOT hardcode secrets in portman-config. Use environment variables and `.env` files. + +## Quick Start + +```sh +# Generate collection from local spec +portman -l ./openapi.yaml + +# Generate and run tests against live API +portman -l ./openapi.yaml -b https://api.example.com -n true + +# With custom config +portman -l ./openapi.yaml -c ./portman-config.json -b https://api.example.com -n true +``` + +## Configuration File + +Create `portman-config.json` (or `.yaml`): + +```json +{ + "version": 1.0, + "tests": { + "contractTests": [], + "contentTests": [], + "variationTests": [], + "integrationTests": [], + "extendTests": [] + }, + "assignVariables": [], + "overwrites": [], + "globals": {} +} +``` + +JSON Schema: `https://raw.githubusercontent.com/apideck-libraries/portman/main/src/utils/portman-config-schema.json` + +## Targeting Operations + +All test and overwrite sections use the same targeting system: + +```json +// By operationId +{ "openApiOperationId": "leadsAdd" } + +// By multiple operationIds +{ "openApiOperationIds": ["leadsAdd", "leadsAll"] } + +// By method::path (supports wildcards) +{ "openApiOperation": "GET::/crm/leads" } +{ "openApiOperation": "*::/crm/*" } +{ "openApiOperation": "POST::/*" } + +// Exclude specific operations +{ "openApiOperation": "*::/crm/*", "excludeForOperations": ["leadsDelete"] } +``` + +## Contract Tests + +Validate API responses conform to the OpenAPI spec: + +```json +{ + "tests": { + "contractTests": [ + { + "openApiOperation": "*::/*", + "statusSuccess": { "enabled": true }, + "contentType": { "enabled": true }, + "jsonBody": { "enabled": true }, + "schemaValidation": { "enabled": true }, + "headersPresent": { "enabled": true } + }, + { + "openApiOperation": "*::/*", + "responseTime": { "enabled": true, "maxMs": 300 } + } + ] + } +} +``` + +| Test | Description | +|------|-------------| +| `statusSuccess` | Response returns 2xx | +| `statusCode` | Response returns specific HTTP code | +| `contentType` | Content-Type matches spec | +| `jsonBody` | Body is valid JSON matching spec | +| `schemaValidation` | Body validates against JSON schema | +| `headersPresent` | Required headers are present | +| `responseTime` | Response within `maxMs` milliseconds | + +## Content Tests + +Validate specific response values: + +```json +{ + "tests": { + "contentTests": [ + { + "openApiOperationId": "leadsAll", + "responseBodyTests": [ + { "key": "status_code", "value": 200 }, + { "key": "data[0].id", "assert": "not.to.be.null" }, + { "key": "data", "minLength": 1 }, + { "key": "resource", "oneOf": ["leads", "contacts"] } + ], + "responseHeaderTests": [ + { "key": "content-type", "contains": "application/json" } + ] + } + ] + } +} +``` + +Content test assertions: `value` (exact), `contains` (substring), `oneOf`, `length`, `minLength`, `maxLength`, `notExist`, `assert` (Postman assertion string). + +## Variation Tests + +Test alternative scenarios (errors, edge cases, unauthorized access): + +```json +{ + "tests": { + "variationTests": [ + { + "openApiOperation": "*::/crm/*", + "openApiResponse": "401", + "variations": [ + { + "name": "Unauthorized", + "overwrites": [ + { "overwriteRequestSecurity": { "bearer": { "token": "invalid" } } } + ], + "tests": { + "contractTests": [{ "statusCode": { "enabled": true } }] + } + } + ] + }, + { + "openApiOperationId": "leadsAdd", + "openApiResponse": "400", + "variations": [ + { + "name": "MissingRequiredFields", + "overwrites": [ + { "overwriteRequestBody": [{ "key": "name", "value": "", "overwrite": true }] } + ], + "tests": { + "contractTests": [ + { "statusCode": { "enabled": true } }, + { "schemaValidation": { "enabled": true } } + ] + } + } + ] + } + ] + } +} +``` + +## Fuzz Testing + +Auto-generate invalid values based on schema constraints: + +```json +{ + "tests": { + "variationTests": [ + { + "openApiOperation": "*::/crm/*", + "openApiResponse": "422", + "variations": [ + { + "name": "FuzzTest", + "fuzzing": [ + { + "requestBody": [ + { + "requiredFields": { "enabled": true }, + "minimumNumberFields": { "enabled": true }, + "maximumNumberFields": { "enabled": true }, + "minLengthFields": { "enabled": true }, + "maxLengthFields": { "enabled": true } + } + ] + } + ], + "tests": { + "contractTests": [{ "statusCode": { "enabled": true } }] + } + } + ] + } + ] + } +} +``` + +Fuzzing targets: `requestBody`, `requestQueryParams`, `requestHeaders`. + +## Integration Tests + +Group operations into end-to-end workflows: + +```json +{ + "tests": { + "integrationTests": [ + { + "name": "Lead Lifecycle", + "operations": [ + { "openApiOperationId": "leadsAdd" }, + { "openApiOperationId": "leadsOne" }, + { "openApiOperationId": "leadsUpdate" }, + { "openApiOperationId": "leadsDelete" } + ] + } + ] + } +} +``` + +## Variable Chaining + +Capture values from responses to use in subsequent requests: + +```json +{ + "assignVariables": [ + { + "openApiOperationId": "leadsAdd", + "collectionVariables": [ + { "responseBodyProp": "data.id", "name": "leadId" }, + { "responseHeaderProp": "x-request-id", "name": "requestId" } + ] + } + ] +} +``` + +Use captured variables in overwrites: `{{leadId}}`, `{{requestId}}`. + +## Request Overwrites + +Modify generated requests: + +```json +{ + "overwrites": [ + { + "openApiOperationId": "leadsAdd", + "overwriteRequestBody": [ + { "key": "name", "value": "Test Lead {{$randomInt}}", "overwrite": true } + ], + "overwriteRequestHeaders": [ + { "key": "x-apideck-consumer-id", "value": "{{consumerId}}", "overwrite": true } + ] + }, + { + "openApiOperation": "DELETE::/crm/leads/{id}", + "overwriteRequestPathVariables": [ + { "key": "id", "value": "{{leadId}}", "overwrite": true } + ] + } + ] +} +``` + +Security overwrites: `overwriteRequestSecurity` supports `bearer`, `apiKey`, `basic`, `oauth2`, and `remove`. + +## Globals + +```json +{ + "globals": { + "collectionPreRequestScripts": ["pm.collectionVariables.set('timestamp', Date.now());"], + "securityOverwrites": { + "bearer": { "token": "{{bearerToken}}" } + }, + "keyValueReplacements": { "x-apideck-app-id": "{{applicationId}}" }, + "valueReplacements": { "": "{{bearerToken}}" }, + "orderOfOperations": ["leadsAdd", "leadsAll", "leadsOne", "leadsUpdate", "leadsDelete"], + "stripResponseExamples": true, + "variableCasing": "camelCase" + } +} +``` + +## Environment Variables + +Variables prefixed with `PORTMAN_` in `.env` are auto-injected as camelCase Postman variables: + +``` +PORTMAN_CONSUMER_ID=test_user → {{consumerId}} +PORTMAN_API_TOKEN=abc123 → {{apiToken}} +``` + +## CI/CD Integration + +Store all options in a CLI options file: + +```json +{ + "local": "./specs/crm.yml", + "baseUrl": "https://staging-api.example.com", + "output": "./output/crm.postman.json", + "portmanConfigFile": "./config/portman-config.json", + "envFile": "./.env", + "includeTests": true, + "runNewman": true +} +``` + +```sh +portman --cliOptionsFile ./portman-cli-options.json +``` + +## Testing Apideck APIs + +```sh +# Test CRM API +portman -u https://specs.apideck.com/crm.yml -c ./portman-config.json -b https://unify.apideck.com -n true + +# Test Accounting API +portman -u https://specs.apideck.com/accounting.yml -c ./portman-config.json -b https://unify.apideck.com -n true +``` + +## CLI Reference + +| Flag | Description | +|------|-------------| +| `-l, --local` | Path to local OpenAPI spec | +| `-u, --url` | URL of remote OpenAPI spec | +| `-b, --baseUrl` | Override base URL | +| `-o, --output` | Output file path | +| `-c, --portmanConfigFile` | Path to portman-config | +| `-n, --runNewman` | Run Newman after generation | +| `-t, --includeTests` | Include test suite (default: true) | +| `-d, --newmanIterationData` | Path to iteration data | +| `--envFile` | Path to .env file | +| `--syncPostman` | Upload to Postman app | +| `--bundleContractTests` | Separate folder for contract tests | +| `--cliOptionsFile` | Path to CLI options file | +| `--init` | Interactive config wizard | diff --git a/skills/apideck-portman/metadata.json b/skills/apideck-portman/metadata.json new file mode 100644 index 0000000..969735c --- /dev/null +++ b/skills/apideck-portman/metadata.json @@ -0,0 +1 @@ +{"version":"1.0.0","organization":"Apideck","date":"February 2026","abstract":"API contract testing with Portman — convert OpenAPI specs to Postman collections with auto-generated tests.","references":["https://github.com/apideck-libraries/portman","https://www.npmjs.com/package/@apideck/portman"]} diff --git a/skills/apideck-python/SKILL.md b/skills/apideck-python/SKILL.md new file mode 100644 index 0000000..85c3496 --- /dev/null +++ b/skills/apideck-python/SKILL.md @@ -0,0 +1,245 @@ +--- +name: apideck-python +description: Apideck Unified API integration patterns for Python. 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 using Python. Covers the apideck-unify SDK, authentication, CRUD operations, pagination, filtering, async support, and Vault connection management. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +# Apideck Python SDK Skill + +## Overview + +The [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 Python SDK (`apideck-unify`) provides typed clients for all unified APIs. + +## Installation + +```sh +pip install apideck-unify +``` + +Requires Python 3.9+. Dependencies: `httpx`, `pydantic`. + +## IMPORTANT RULES + +- ALWAYS use the `apideck-unify` SDK. DO NOT make raw `httpx`/`requests` calls to the Apideck API. +- ALWAYS pass `api_key`, `app_id`, and `consumer_id` when initializing the client. +- ALWAYS set the `APIDECK_API_KEY` environment variable rather than hardcoding API keys. +- USE `service_id` to specify which downstream connector to use (e.g., `"salesforce"`, `"quickbooks"`). If a consumer has multiple connections for an API, `service_id` is required. +- USE context managers (`with` / `async with`) for client lifecycle management. +- USE the `fields` parameter to request only the columns you need. +- USE the `filter_` parameter (note the trailing underscore) to narrow results server-side. +- ALWAYS handle errors with try/except using `models.ApideckError` as the base class. + +## Quick Start + +```python +from apideck_unify import Apideck +import os + +with Apideck( + api_key=os.getenv("APIDECK_API_KEY", ""), + app_id="your-app-id", + consumer_id="your-consumer-id", +) as apideck: + res = apideck.crm.contacts.list( + service_id="salesforce", + limit=20, + filter_={"email": "john@example.com"}, + ) + while res is not None: + for contact in res.data: + print(contact.name, contact.emails) + res = res.next() +``` + +## SDK Patterns + +### Client Setup + +```python +from apideck_unify import Apideck +import os + +with Apideck( + api_key=os.getenv("APIDECK_API_KEY", ""), + app_id="your-app-id", + consumer_id="your-consumer-id", +) as apideck: + # Make API calls here + pass +``` + +The `consumer_id` 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}()`. + +```python +import apideck_unify +from apideck_unify import Apideck +import os + +with Apideck( + api_key=os.getenv("APIDECK_API_KEY", ""), + app_id="your-app-id", + consumer_id="your-consumer-id", +) as apideck: + + # LIST - retrieve multiple records + res = apideck.crm.contacts.list( + service_id="salesforce", + limit=20, + filter_={"email": "john@example.com", "company_id": "12345"}, + sort={"by": apideck_unify.ContactsSortBy.CREATED_AT, "direction": apideck_unify.SortDirection.DESC}, + fields="id,name,email", + ) + + # CREATE - create a new record + res = apideck.crm.contacts.create( + service_id="salesforce", + first_name="John", + last_name="Doe", + emails=[{"email": "john@example.com", "type": apideck_unify.EmailType.PRIMARY}], + phone_numbers=[{"number": "+1234567890", "type": apideck_unify.PhoneNumberType.PRIMARY}], + ) + print(res.create_contact_response) + + # GET - retrieve a single record + res = apideck.crm.contacts.get(id="contact_123", service_id="salesforce") + + # UPDATE - modify an existing record + res = apideck.crm.contacts.update(id="contact_123", service_id="salesforce", first_name="Jane") + + # DELETE - remove a record + res = apideck.crm.contacts.delete(id="contact_123", service_id="salesforce") +``` + +### Pagination + +Use the `.next()` method on response objects for cursor-based pagination: + +```python +res = apideck.accounting.invoices.list(service_id="quickbooks", limit=50) + +while res is not None: + for invoice in res.data: + print(invoice.number, invoice.total) + res = res.next() +``` + +### Async Support + +Every sync method has an `_async` counterpart. Use `async with` as context manager: + +```python +import asyncio +from apideck_unify import Apideck +import os + +async def main(): + async with Apideck( + api_key=os.getenv("APIDECK_API_KEY", ""), + app_id="your-app-id", + consumer_id="your-consumer-id", + ) as apideck: + res = await apideck.crm.contacts.list_async( + service_id="salesforce", + limit=20, + ) + while res is not None: + for contact in res.data: + print(contact.name) + res = res.next() + +asyncio.run(main()) +``` + +### Error Handling + +```python +from apideck_unify import Apideck, models + +try: + res = apideck.crm.contacts.get(id="invalid", service_id="salesforce") +except models.BadRequestResponse as e: + print("Bad request:", e.message, e.status_code) +except models.UnauthorizedResponse as e: + print("Invalid API key or missing credentials") +except models.NotFoundResponse as e: + print("Record not found") +except models.PaymentRequiredResponse as e: + print("API limit reached") +except models.UnprocessableResponse as e: + print("Validation error:", e.message) +except models.ApideckError as e: + print(f"API error {e.status_code}: {e.message}") +``` + +All exceptions inherit from `models.ApideckError` with properties: `message`, `status_code`, `headers`, `body`, `raw_response`. + +### Common Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `service_id` | `str` | Downstream connector ID (e.g., `"quickbooks"`, `"salesforce"`) | +| `limit` | `int` | Max results per page (1-200, default 20) | +| `cursor` | `str` | Pagination cursor from previous response | +| `filter_` | `dict` | Resource-specific filter criteria (note trailing underscore) | +| `sort` | `dict` | `{"by": SortField, "direction": SortDirection}` | +| `fields` | `str` | Comma-separated field names to return | +| `pass_through` | `dict` | Pass-through query parameters for the downstream API | +| `raw` | `bool` | Include raw downstream response when `True` | +| `retry_config` | `RetryConfig` | Per-call retry override | + +### Pass-Through Parameters + +```python +# Query pass-through +res = apideck.accounting.invoices.list( + service_id="quickbooks", + pass_through={"search": "overdue"}, +) + +# Body pass-through for connector-specific fields +res = apideck.crm.contacts.create( + service_id="salesforce", + first_name="John", + last_name="Doe", + pass_through=[{ + "service_id": "salesforce", + "operation_id": "contactsAdd", + "extend_object": {"custom_sf_field__c": "value"}, + }], +) +``` + +### Retry Configuration + +```python +from apideck_unify import Apideck +from apideck_unify.utils import BackoffStrategy, RetryConfig + +with Apideck( + api_key=os.getenv("APIDECK_API_KEY", ""), + app_id="your-app-id", + consumer_id="your-consumer-id", + retry_config=RetryConfig("backoff", BackoffStrategy(1, 50, 1.1, 100), False), +) as apideck: + pass +``` + +## API Namespaces + +| Namespace | Resources | +|-----------|-----------| +| `apideck.accounting.*` | invoices, bills, payments, customers, suppliers, ledger_accounts, journal_entries, tax_rates, credit_notes, purchase_orders, balance_sheet, profit_and_loss, expenses, attachments, and more | +| `apideck.crm.*` | contacts, companies, leads, opportunities, activities, notes, pipelines, users | +| `apideck.hris.*` | employees, companies, departments, payrolls, time_off_requests | +| `apideck.file_storage.*` | files, folders, drives, shared_links, upload_sessions | +| `apideck.ats.*` | applicants, applications, jobs | +| `apideck.vault.*` | connections, consumers, sessions, custom_mappings, logs | +| `apideck.webhook.*` | webhooks, event_logs | diff --git a/skills/apideck-python/metadata.json b/skills/apideck-python/metadata.json new file mode 100644 index 0000000..465cfbc --- /dev/null +++ b/skills/apideck-python/metadata.json @@ -0,0 +1 @@ +{"version":"1.0.0","organization":"Apideck","date":"February 2026","abstract":"Python SDK patterns for all Apideck Unified APIs with async support.","references":["https://developers.apideck.com","https://pypi.org/project/apideck-unify/"]} diff --git a/skills/apideck-rest/SKILL.md b/skills/apideck-rest/SKILL.md new file mode 100644 index 0000000..3b54bc8 --- /dev/null +++ b/skills/apideck-rest/SKILL.md @@ -0,0 +1,301 @@ +--- +name: apideck-rest +description: Apideck Unified REST API reference for any language. 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 using direct HTTP calls. Covers authentication headers, CRUD operations, cursor-based pagination, filtering, sorting, error handling, rate limiting, pass-through parameters, and webhooks. Language-agnostic — works with curl, fetch, axios, httpx, or any HTTP client. +license: Apache-2.0 +alwaysApply: false +metadata: + author: apideck + version: "1.0.0" +--- + +# Apideck REST API Skill + +## Overview + +The [Apideck Unified API](https://apideck.com) provides a single REST endpoint to connect with 200+ third-party services across accounting, CRM, HRIS, file storage, ATS, e-commerce, and more. This skill covers direct HTTP usage for any language. + +**Base URL:** `https://unify.apideck.com` + +## IMPORTANT RULES + +- ALWAYS include the three required headers: `Authorization`, `x-apideck-app-id`, and `x-apideck-consumer-id`. +- ALWAYS make API calls server-side to prevent token leakage. +- USE `x-apideck-service-id` to specify which downstream connector to use. Required when a consumer has multiple connections for the same API. +- USE cursor-based pagination — iterate until `meta.cursors.next` is `null`. +- USE the `filter` query parameters to narrow results server-side. DO NOT fetch all records and filter client-side. +- USE the `fields` query parameter to request only the columns you need. +- DO NOT store API keys in source code. Use environment variables. + +## Authentication + +Every request requires these headers: + +| Header | Required | Description | +|--------|----------|-------------| +| `Authorization` | Yes | `Bearer {API_KEY}` | +| `x-apideck-app-id` | Yes | Your Apideck application ID | +| `x-apideck-consumer-id` | Yes | End-user/customer ID stored in Vault | +| `x-apideck-service-id` | No | Downstream connector ID (e.g., `salesforce`, `quickbooks`) | +| `Content-Type` | Yes (POST/PATCH) | `application/json` | + +## CRUD Operations + +All resources follow a consistent URL pattern: + +``` +GET /{api}/{resource} → List +POST /{api}/{resource} → Create +GET /{api}/{resource}/{id} → Get +PATCH /{api}/{resource}/{id} → Update +DELETE /{api}/{resource}/{id} → Delete +``` + +### List + +```bash +curl -X GET 'https://unify.apideck.com/crm/contacts?limit=20&filter[email]=john@example.com&sort[by]=updated_at&sort[direction]=desc&fields=id,name,email' \ + -H 'Authorization: Bearer {API_KEY}' \ + -H 'x-apideck-app-id: {APP_ID}' \ + -H 'x-apideck-consumer-id: {CONSUMER_ID}' \ + -H 'x-apideck-service-id: salesforce' +``` + +Response: +```json +{ + "status_code": 200, + "status": "OK", + "service": "salesforce", + "resource": "contacts", + "operation": "all", + "data": [ + { "id": "contact_123", "name": "John Doe", "email": "john@example.com" } + ], + "meta": { + "items_on_page": 20, + "cursors": { + "previous": null, + "current": "em9oby1jcm06Om9mZnNldDo6MA==", + "next": "em9oby1jcm06Om9mZnNldDo6MjA=" + } + }, + "links": { + "previous": null, + "current": "https://unify.apideck.com/crm/contacts?cursor=...", + "next": "https://unify.apideck.com/crm/contacts?cursor=..." + } +} +``` + +### Create + +```bash +curl -X POST 'https://unify.apideck.com/crm/contacts' \ + -H 'Authorization: Bearer {API_KEY}' \ + -H 'Content-Type: application/json' \ + -H 'x-apideck-app-id: {APP_ID}' \ + -H 'x-apideck-consumer-id: {CONSUMER_ID}' \ + -H 'x-apideck-service-id: salesforce' \ + -d '{ + "first_name": "John", + "last_name": "Doe", + "title": "VP of Engineering", + "emails": [{"email": "john@example.com", "type": "primary"}], + "phone_numbers": [{"number": "+1234567890", "type": "mobile"}], + "addresses": [{ + "type": "primary", + "street_1": "123 Main St", + "city": "San Francisco", + "state": "CA", + "postal_code": "94105", + "country": "US" + }] + }' +``` + +Response: `201 Created` with `{ "data": { "id": "contact_123" } }` + +### Get + +```bash +curl -X GET 'https://unify.apideck.com/crm/contacts/contact_123' \ + -H 'Authorization: Bearer {API_KEY}' \ + -H 'x-apideck-app-id: {APP_ID}' \ + -H 'x-apideck-consumer-id: {CONSUMER_ID}' \ + -H 'x-apideck-service-id: salesforce' +``` + +### Update + +```bash +curl -X PATCH 'https://unify.apideck.com/crm/contacts/contact_123' \ + -H 'Authorization: Bearer {API_KEY}' \ + -H 'Content-Type: application/json' \ + -H 'x-apideck-app-id: {APP_ID}' \ + -H 'x-apideck-consumer-id: {CONSUMER_ID}' \ + -H 'x-apideck-service-id: salesforce' \ + -d '{"title": "CTO"}' +``` + +### Delete + +```bash +curl -X DELETE 'https://unify.apideck.com/crm/contacts/contact_123' \ + -H 'Authorization: Bearer {API_KEY}' \ + -H 'x-apideck-app-id: {APP_ID}' \ + -H 'x-apideck-consumer-id: {CONSUMER_ID}' \ + -H 'x-apideck-service-id: salesforce' +``` + +## Pagination + +Apideck uses cursor-based pagination. Pass the `next` cursor from the response to fetch subsequent pages: + +| Parameter | Type | Default | Range | +|-----------|------|---------|-------| +| `limit` | integer | 20 | 1-200 | +| `cursor` | string | — | Opaque cursor from `meta.cursors.next` | + +```bash +# First page +curl 'https://unify.apideck.com/crm/contacts?limit=50' -H '...' + +# Next page +curl 'https://unify.apideck.com/crm/contacts?limit=50&cursor=em9oby1jcm06Om9mZnNldDo6NTA=' -H '...' +``` + +When `meta.cursors.next` is `null`, you have reached the last page. + +## Filtering and Sorting + +### Filters + +``` +?filter[field_name]=value +``` + +Available filters vary by resource. Common examples: + +| Resource | Filters | +|----------|---------| +| CRM Contacts | `filter[name]`, `filter[email]`, `filter[phone_number]`, `filter[company_id]`, `filter[owner_id]`, `filter[first_name]`, `filter[last_name]` | +| CRM Opportunities | `filter[status]`, `filter[title]`, `filter[company_id]`, `filter[owner_id]` | +| Accounting Invoices | `filter[updated_since]` (ISO 8601 datetime) | +| General | `filter[updated_since]` for incremental sync | + +### Sorting + +``` +?sort[by]=updated_at&sort[direction]=desc +``` + +### Field Selection + +``` +?fields=id,name,email,phone_numbers +``` + +## Pass-Through Parameters + +For connector-specific query parameters not in the unified model: + +``` +?pass_through[search]=overdue +``` + +For connector-specific fields in request bodies: + +```json +{ + "first_name": "John", + "pass_through": [ + { + "service_id": "salesforce", + "operation_id": "contactsAdd", + "extend_object": { + "custom_sf_field__c": "value" + } + } + ] +} +``` + +## Error Handling + +All errors follow this format: + +```json +{ + "status_code": 400, + "error": "Bad Request", + "type_name": "RequestValidationError", + "message": "Human-readable error description", + "detail": "Parameter-specific info", + "ref": "https://developers.apideck.com/errors#requestvalidationerror" +} +``` + +| Code | Meaning | +|------|---------| +| 400 | Bad Request — invalid parameters | +| 401 | Unauthorized — invalid API key | +| 402 | Payment Required — API limit reached | +| 404 | Not Found — resource does not exist | +| 422 | Unprocessable Entity — validation error | +| 429 | Too Many Requests — rate limit exceeded | +| 5xx | Server Error — Apideck or downstream failure | + +## Rate Limiting + +Apideck normalizes downstream rate limit headers: + +| Header | Description | +|--------|-------------| +| `x-downstream-ratelimit-limit` | Total request capacity | +| `x-downstream-ratelimit-remaining` | Remaining requests | +| `x-downstream-ratelimit-reset` | Unix timestamp when limits reset | + +## Raw Mode + +Append `?raw=true` to include the unmodified downstream response in a `_raw` property alongside normalized data. + +## Available API Endpoints + +| API | URL Prefix | Resources | +|-----|-----------|-----------| +| CRM | `/crm/` | contacts, companies, leads, opportunities, activities, notes, pipelines, users | +| Accounting | `/accounting/` | invoices, bills, payments, customers, suppliers, ledger-accounts, journal-entries, tax-rates, credit-notes, purchase-orders, balance-sheet, profit-and-loss | +| HRIS | `/hris/` | employees, companies, departments, payrolls, time-off-requests | +| File Storage | `/file-storage/` | files, folders, drives, drive-groups, shared-links, upload-sessions | +| ATS | `/ats/` | applicants, applications, jobs | +| Vault | `/vault/` | connections, sessions, consumers, custom-mappings, logs | +| Webhook | `/webhook/` | webhooks, event-logs | + +## Webhook Events + +Events follow the pattern `{api}.{resource}.{action}`: + +``` +crm.contact.created / .updated / .deleted +accounting.invoice.created / .updated / .deleted +hris.employee.created / .updated / .deleted / .terminated +file-storage.file.created / .updated / .deleted +ats.applicant.created / .updated / .deleted +``` + +Payload: +```json +{ + "payload": { + "event_type": "crm.contact.updated", + "unified_api": "crm", + "service_id": "salesforce", + "consumer_id": "user_abc123", + "entity_id": "contact_123", + "entity_type": "contact", + "occurred_at": "2024-06-15T10:30:00.000Z" + } +} +``` + +Verify signatures using the `x-apideck-signature` header with HMAC-SHA256. diff --git a/skills/apideck-rest/metadata.json b/skills/apideck-rest/metadata.json new file mode 100644 index 0000000..8f16485 --- /dev/null +++ b/skills/apideck-rest/metadata.json @@ -0,0 +1 @@ +{"version":"1.0.0","organization":"Apideck","date":"February 2026","abstract":"Language-agnostic REST API reference for direct HTTP integration with Apideck.","references":["https://developers.apideck.com","https://specs.apideck.com"]} diff --git a/skills/sync.js b/skills/sync.js new file mode 100644 index 0000000..acd3c54 --- /dev/null +++ b/skills/sync.js @@ -0,0 +1,225 @@ +#!/usr/bin/env node + +/** + * Sync script for Apideck API Skills + * + * Fetches the latest OpenAPI specs from specs.apideck.com and updates + * skill reference files with current endpoint information. Also syncs + * skills to provider plugin directories (Claude, Cursor). + * + * Usage: + * node skills/sync.js # Sync all + * node skills/sync.js --skills-only # Only sync to provider dirs + * node skills/sync.js --specs-only # Only update from OpenAPI specs + * + * Environment: + * APIDECK_SPECS_BASE_URL Override specs base URL (default: https://specs.apideck.com) + */ + +const fs = require("fs"); +const path = require("path"); +const https = require("https"); + +const SPECS_BASE_URL = + process.env.APIDECK_SPECS_BASE_URL || "https://specs.apideck.com"; + +const APIS = [ + "accounting", + "crm", + "hris", + "file-storage", + "ats", + "vault", + "webhook", + "ecommerce", + "issue-tracking", + "sms", + "lead", + "pos", + "connector", + "proxy", +]; + +const SKILLS_DIR = path.join(__dirname); +const PROVIDERS_DIR = path.join(__dirname, "..", "providers"); +const PROVIDER_TARGETS = [ + path.join(PROVIDERS_DIR, "claude", "plugin", "skills"), + path.join(PROVIDERS_DIR, "cursor", "plugin", "skills"), +]; + +// ── Skill Sync ────────────────────────────────────────────────────────────── + +function getSkillDirs() { + return fs + .readdirSync(SKILLS_DIR, { withFileTypes: true }) + .filter( + (d) => d.isDirectory() && d.name.startsWith("apideck-") && d.name !== "apideck-node" + ) + .map((d) => d.name); +} + +function getNodeSkillDirs() { + const nodeSkillDir = path.join(SKILLS_DIR, "apideck-node"); + if (!fs.existsSync(nodeSkillDir)) return []; + return ["apideck-node"]; +} + +function syncSkillToProviders(skillName) { + const srcDir = path.join(SKILLS_DIR, skillName); + const skillFile = path.join(srcDir, "SKILL.md"); + + if (!fs.existsSync(skillFile)) { + console.warn(` SKIP ${skillName}: no SKILL.md found`); + return; + } + + for (const targetBase of PROVIDER_TARGETS) { + const targetDir = path.join(targetBase, skillName); + fs.mkdirSync(targetDir, { recursive: true }); + + // Copy SKILL.md + fs.copyFileSync(skillFile, path.join(targetDir, "SKILL.md")); + + // Copy references/ if present + const refsDir = path.join(srcDir, "references"); + if (fs.existsSync(refsDir)) { + const targetRefsDir = path.join(targetDir, "references"); + fs.mkdirSync(targetRefsDir, { recursive: true }); + for (const file of fs.readdirSync(refsDir)) { + fs.copyFileSync( + path.join(refsDir, file), + path.join(targetRefsDir, file) + ); + } + } + } + + console.log(` OK ${skillName} -> ${PROVIDER_TARGETS.length} providers`); +} + +function syncAllSkills() { + console.log("Syncing skills to provider directories...\n"); + + const allSkills = [ + ...getSkillDirs(), + ...getNodeSkillDirs(), + ]; + + for (const skill of allSkills) { + syncSkillToProviders(skill); + } + + // Also copy commands to Cursor + const claudeCommands = path.join( + PROVIDERS_DIR, + "claude", + "plugin", + "commands" + ); + const cursorCommands = path.join( + PROVIDERS_DIR, + "cursor", + "plugin", + "commands" + ); + + if (fs.existsSync(claudeCommands)) { + fs.mkdirSync(cursorCommands, { recursive: true }); + for (const file of fs.readdirSync(claudeCommands)) { + fs.copyFileSync( + path.join(claudeCommands, file), + path.join(cursorCommands, file) + ); + } + console.log(` OK commands -> cursor`); + } + + console.log("\nDone.\n"); +} + +// ── OpenAPI Spec Fetch ────────────────────────────────────────────────────── + +function fetch(url) { + return new Promise((resolve, reject) => { + https + .get(url, (res) => { + if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { + return fetch(res.headers.location).then(resolve).catch(reject); + } + if (res.statusCode !== 200) { + return reject(new Error(`HTTP ${res.statusCode} for ${url}`)); + } + let data = ""; + res.on("data", (chunk) => (data += chunk)); + res.on("end", () => resolve(data)); + }) + .on("error", reject); + }); +} + +async function fetchSpecInfo(apiName) { + const url = `${SPECS_BASE_URL}/${apiName}.yml`; + try { + const spec = await fetch(url); + // Extract basic info from the YAML (lightweight parse) + const titleMatch = spec.match(/^\s*title:\s*(.+)$/m); + const versionMatch = spec.match(/^\s*version:\s*(.+)$/m); + const pathCount = (spec.match(/^\s{2}\/[^\s:]+:/gm) || []).length; + + return { + api: apiName, + title: titleMatch ? titleMatch[1].trim().replace(/['"]/g, "") : apiName, + version: versionMatch + ? versionMatch[1].trim().replace(/['"]/g, "") + : "unknown", + endpoints: pathCount, + url, + }; + } catch (err) { + console.warn(` SKIP ${apiName}: ${err.message}`); + return null; + } +} + +async function updateSpecsInfo() { + console.log("Fetching OpenAPI spec info...\n"); + + const results = []; + for (const api of APIS) { + const info = await fetchSpecInfo(api); + if (info) { + results.push(info); + console.log( + ` OK ${info.api}: v${info.version}, ${info.endpoints} paths` + ); + } + } + + // Write spec summary + const summaryPath = path.join(SKILLS_DIR, "..", "specs-summary.json"); + fs.writeFileSync(summaryPath, JSON.stringify(results, null, 2) + "\n"); + console.log(`\nWrote ${summaryPath}\n`); + + return results; +} + +// ── Main ──────────────────────────────────────────────────────────────────── + +async function main() { + const args = process.argv.slice(2); + const skillsOnly = args.includes("--skills-only"); + const specsOnly = args.includes("--specs-only"); + + if (!skillsOnly) { + await updateSpecsInfo(); + } + + if (!specsOnly) { + syncAllSkills(); + } +} + +main().catch((err) => { + console.error(err); + process.exit(1); +});