mcpField
F
factorial-api-sdks

factorial-api-sdks

github.com/factorialcoDevelopment7357 installsUpdated 4d ago
Repository

About factorial-api-sdks

A skill for using Factorial’s official API SDKs in TypeScript, Python, and Ruby, and handling Factorial webhooks. It explains namespaced clients, authentication, pagination, errors, and typed webhook payloads.

Key features

  1. 01Official SDKs for three languages
  2. 02API key and OAuth authentication
  3. 03Namespaced endpoint clients
  4. 04Pagination helpers
  5. 05Typed webhook payloads

Use cases

  1. 01Calling Factorial API endpoints
  2. 02Retrieving large datasets page by page
  3. 03Managing webhook subscriptions
  4. 04Handling and typing webhook events

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/factorialco/factorial-api-sdks --skill factorial-api-sdks

After installing, type/factorial-api-sdksin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

reference/
ruby-methods.json
sdk-methods.md
webhooks.md

Details

From the project README

Factorial API SDKs

Official SDKs that wrap the Factorial public API with a domain-namespaced client, pagination helpers, and clean auth. This skill teaches an agent how to use them and how to work with Factorial webhooks. Generated reference data lives in reference/ (see References).

  • TypeScript: @factorialco/api-client (npm)
  • Python: factorial-api-client (PyPI)
  • Ruby: factorial_api (RubyGems)

Install

npm install @factorialco/api-client     # TypeScript
pip install factorial-api-client          # Python
gem install factorial_api                 # Ruby

Authentication

Provide either an API key or an OAuth token (or both):

OptionHeader sent
apiKeyx-api-key: <value>
tokenAuthorization: Bearer <value>

The Factorial API key is JWT-formatted but is sent as x-api-key, not as a bearer token.

Credentials can be passed explicitly or read from the environment (FACTORIAL_API_KEY, FACTORIAL_TOKEN, optional FACTORIAL_BASE_URL).

import { FactorialClient } from "@factorialco/api-client";
const client = new FactorialClient({ apiKey: process.env.FACTORIAL_API_KEY });
from factorial_api_client import FactorialClient
client = FactorialClient(api_key="YOUR_KEY")   # or token="...", or from env
require "factorial_api"
api = F::Api.new(api_key: ENV["FACTORIAL_API_KEY"])   # or token:, or from env

Calling endpoints

The client is organized as client.<namespace>.<resource>.<method>. Namespaces and resources mirror the REST path /api/<version>/resources/<namespace>/<resource>.

Method naming is deterministic:

HTTP / shapeSDK method
GET collectionlist() / paginate() / all()
GET by idget(id)
POST collectioncreate(body)
PUT/PATCH by idupdate(id, body)
DELETE by iddelete(id)
Custom action (e.g. apply)camelCased action, e.g. apply()
const { data } = await client.employees.employees.list();
const created = await client.apiPublic.webhookSubscriptions.create({ /* body */ });
employees = client.employees.employee.list()
sub = client.api_public.webhook_subscription.create(body={...})

Ruby follows the same shape in snake_case, with required params — path, query or form alike — as named keywords; optional query params go in a query_params: hash and bodies under their *_request keyword. (The raw low-level accessors, e.g. api.employees_employee.employees_employees_get(true, false), keep working underneath.)

employees = api.employees.employee.list(only_active: true, only_managers: false).data
team = api.teams.team.create(teams_teams_post_request: { name: "Platform" })

To discover the exact namespace/resource/method for any endpoint, consult reference/sdk-methods.md (every endpoint with its TS and Ruby calls, grouped) or the online API reference.

Pagination

List endpoints return { data: T[], meta: { end_cursor?, has_next_page, ... } }.

  • list() — one raw page
  • paginate() — async iterator (TS) / sync or async generator (Python)
  • all() (TS) / collect_all() (Python) — fetch every page into one array/list
  • Ruby: paginate (lazy Enumerator, caps via limit:/max_items:) and all on every listable resource; F::Api.paginate { |page| ... } wraps raw calls
for await (const emp of client.employees.employees.paginate()) { /* ... */ }
const everyone = await client.employees.employees.all({ maxItems: 500 });
for emp in client.employees.employee.paginate(max_items=50): ...
everyone = client.employees.employee.all()
High-volume retrieval

Pages are capped at 100 items — a server-side hard max, not a default (see the pagination docs). Passing a larger limit has no effect. Cursor pagination is sequential (page N+1 needs page N's end_cursor), so all() on a big dataset (e.g. a full company-month of attendance/worked_times) means dozens of sequential requests. To keep request counts and latency sane:

  • Filter first. Use the endpoint's query params (date ranges, ids, employee_ids, …) instead of pulling everything and filtering locally.
  • Sync incrementally. Where an endpoint supports updated_at-style filters, fetch only what changed since your last run and cache locally — don't re-pull the full dataset per report.
  • Shard big pulls. Split one large query into filtered sub-queries (by date window or id chunks) and run those concurrently — each sub-query still paginates sequentially, but wall-clock time drops. Total request count is unchanged, so watch rate limits.
  • Cap defensively. Pass max_items / maxItems so a bug or unexpected data volume can't turn into an unbounded crawl.

There is no server-side aggregation endpoint; totals (e.g. hours per employee per period) must be computed client-side from the raw records.

Errors

Every SDK fails loudly on a non-2xx response (bad/expired token, wrong base URL, server errors) instead of silently returning empty data. Each raises a typed error carrying the status and the response body:

SDKClassStatusBody
TypeScriptFactorialApiError (extends Error)statusbody (parsed), plus method, url
PythonUnexpectedStatusstatus_codecontent (bytes)
RubyF::Api::ApiErrorcoderesponse_body
// TypeScript — results have no `error` field; failures throw.
import { FactorialApiError } from "@factorialco/api-client";
try {
  const { data } = await client.employees.employees.get({ path: { id: "42" } });
} catch (err) {
  if (err instanceof FactorialApiError) console.error(err.status, err.url, err.body);
  else throw err; // transport failure (DNS/connection), not an HTTP response
}
# Python
from factorial_api_client.generated.errors import UnexpectedStatus
try:
    employees = client.employees.employees.list()
except UnexpectedStatus as e:
    print(e.status_code, e.content)
# Ruby
begin
  api.employees.employee.list
rescue F::Api::ApiError => e
  puts e.code, e.response_body
end

API versioning

The SDK targets a specific Factorial API date version (e.g. 2026-04-01), pinned by the SDK's major version. Newer API versions ship as new SDK majors.

Webhooks

Webhooks are HTTP POSTs Factorial sends to your target_url when an event happens.

How delivery works

  • You create a webhook subscription for a subscription_type (e.g. ats/application/create), pointing at a target_url, with your company_id.
  • When the event fires, Factorial sends a POST to target_url. The payload is the resource object at the top level — it is not wrapped in a { type, data } envelope. Use a distinct URL per event if you need to tell them apart easily.
  • If you set a challenge when subscribing, Factorial echoes it back in the x-factorial-wh-challenge request header so you can verify the source.
  • Optional author headers x-factorial-author-id / x-factorial-author-type (employee or company) identify who triggered the event, when safe to expose.
  • Retry policy: up to 20 retries over 48h; after that the subscription is disabled and you are emailed. Re-enable with a PUT { enabled: true }.

Manage subscriptions via the SDK

await client.apiPublic.webhookSubscriptions.create({
  subscription_type: "ats/application/create",
  target_url: "https://example.com/webhooks/factorial",
  challenge: "a-secret-you-choose",
  company_id: 55,
});
client.api_public.webhook_subscription.create(body={
    "subscription_type": "ats/application/create",
    "target_url": "https://example.com/webhooks/factorial",
    "challenge": "a-secret-you-choose",
    "company_id": 55,
})

Type the handler payload with the exported webhook types:

import type { AtsApplicationCreateWebhook, WebhookPayloadMap, WebhookSubscriptionType }
  from "@factorialco/api-client";

// Direct alias
function onApplicationCreated(payload: AtsApplicationCreateWebhook) { /* ... */ }

// Typed dispatch keyed on the runtime subscription_type
function handle<T extends WebhookSubscriptionType>(type: T, payload: WebhookPayloadMap[T]) { /* ... */ }
from factorial_api_client import AtsApplicationCreateWebhook, WEBHOOK_PAYLOAD_TYPES

def on_application_created(payload: AtsApplicationCreateWebhook) -> None: ...

# Runtime lookup of the model class for a subscription_type
model_cls = WEBHOOK_PAYLOAD_TYPES["ats/application/create"]

The full list of events, their subscription_type, and payload fields is in reference/webhooks.md.

References

Generated, SDK-specific lookup tables (consult these for exact names/shapes):

  • reference/webhooks.md — every webhook event, its subscription_type, and payload fields.
  • reference/sdk-methods.md — every REST endpoint grouped by namespace/resource, with the SDK call.

For prose guides (OAuth flows, API keys, versioning, webhook policies, etc.), use the live docs — always current, not vendored here:

Comments

Markdown supported