@freeloapp/js-sdk

中文文档
2.7.5发布于 2026/11/03MITTypeScriptESM + CJS零依赖构建来源已验证近 30 天下载 2168

Official JavaScript/TypeScript SDK for Freelo.io API

原始使用文档
文档目录30 节
  1. Features
  2. Installation
  3. Quick Start
  4. Authentication
  5. Basic Auth
  6. Bearer Token
  7. OAuth 2.1 (PKCE)
  8. Configuration
  9. Default headers
  10. Multi-Tenant / Per-Request Auth
  11. API Reference
  12. Projects
  13. Tasks
  14. Task Labels
  15. Notifying yourself about your own actions
  16. Tasklists
  17. Comments
  18. Time Tracking
  19. Search
  20. Pagination
  21. Error Handling
  22. Rate Limiting
  23. Utility Functions
  24. Date Utilities
  25. Currency Utilities
  26. Updating SDK When API Changes
  27. TypeScript Support
  28. Migration from v1
  29. Contributing
  30. License

Freelo SDK

Official JavaScript/TypeScript SDK for Freelo.io API.

npm version TypeScript License: MIT

Features

  • Auto-generated from OpenAPI — always in sync with the Freelo API
  • Full TypeScript support with type definitions
  • Zero runtime dependencies (uses native fetch)
  • Tree-shakeable — import only what you need
  • Works in Node.js 18+, browsers, and all major frameworks
  • Promise-based async/await API
  • Per-request auth for multi-tenant scenarios
  • Built-in pagination, currency, and date utilities

Installation

npm install @freeloapp/js-sdk

Quick Start

import { createFreelo, getProjects, createTask } from '@freeloapp/js-sdk';

// Initialize client (sets global default)
createFreelo({
  auth: { type: 'basic', email: '[email protected]', apiKey: 'your-api-key' },
  userAgent: 'YourApp/1.0 ([email protected])',
});

// Get all projects
const { data: projects } = await getProjects();

// Create a task
const { data: task } = await createTask({
  path: { tasklist_id: 123 },
  body: {
    name: 'New Task',
    worker_ids: [userId],
  },
});

Authentication

The SDK supports three authentication methods:

Basic Auth

Get your API key from Freelo Settings.

createFreelo({
  auth: { type: 'basic', email: '[email protected]', apiKey: 'your-api-key' },
  userAgent: 'YourApp/1.0',
});

Bearer Token

Use a pre-obtained JWT or PASETO token:

createFreelo({
  auth: { type: 'bearer', token: 'your-jwt-token' },
  userAgent: 'YourApp/1.0',
});

OAuth 2.1 (PKCE)

For applications that authenticate users via Freelo's identity provider (identity.freelo.io). The SDK handles automatic token refresh — you handle the initial authorization flow.

Step 1: Start the OAuth flow

import {
  generatePKCEChallenge,
  buildAuthorizationUrl,
} from '@freeloapp/js-sdk';

// Generate PKCE challenge (store verifier for step 2)
const pkce = await generatePKCEChallenge();
sessionStorage.setItem('pkce_verifier', pkce.codeVerifier);

// Build authorization URL and redirect the user
const authUrl = buildAuthorizationUrl({
  clientId: 'your-client-id',
  redirectUri: 'https://yourapp.com/callback',
  codeChallenge: pkce.codeChallenge,
  scope: ['read', 'write', 'projects', 'tasks'],
  state: crypto.randomUUID(),
});
window.location.href = authUrl;

Step 2: Exchange the authorization code for tokens

import { exchangeCode } from '@freeloapp/js-sdk';

// In your callback handler (e.g. /callback)
const params = new URLSearchParams(window.location.search);
const tokens = await exchangeCode({
  code: params.get('code')!,
  codeVerifier: sessionStorage.getItem('pkce_verifier')!,
  clientId: 'your-client-id',
  redirectUri: 'https://yourapp.com/callback',
});

Step 3: Create the SDK client

import { createFreelo, getProjects } from '@freeloapp/js-sdk';

createFreelo({
  auth: {
    type: 'oauth',
    accessToken: tokens.accessToken,
    refreshToken: tokens.refreshToken,
    clientId: 'your-client-id',
    expiresAt: tokens.expiresAt,
    onTokenRefreshed: (newTokens) => {
      // Persist new tokens (localStorage, database, etc.)
      localStorage.setItem('freelo_tokens', JSON.stringify(newTokens));
    },
  },
  userAgent: 'YourApp/1.0',
});

// Use the SDK normally — token refresh happens automatically
const { data: projects } = await getProjects();

The SDK refreshes tokens proactively (30s before expiry) and reactively (on 401 responses). Concurrent requests share a single refresh call.

Additional OAuth utilities:

import {
  refreshAccessToken,    // Manually refresh a token
  revokeToken,           // Revoke an access or refresh token
  discoverOAuthServer,   // Fetch OAuth server metadata (RFC 8414)
} from '@freeloapp/js-sdk';

Configuration

import { createFreelo } from '@freeloapp/js-sdk';

const client = createFreelo({
  auth: {                        // Required: authentication credentials
    type: 'basic',               //   Basic Auth with email + API key
    email: '[email protected]',
    apiKey: 'your-api-key',
  },
  userAgent: 'YourApp/1.0',      // Required: Identifies your application
  baseUrl: 'https://api.freelo.io/v1',  // Optional: API base URL
  logging: true,                 // Optional: Log requests to console
  headers: {                     // Optional: default headers on every request
    'X-Custom-Header': 'custom-value',
  },
});

Default headers

Use the headers option when you need a header sent on every request — for example to add tracing or client-identification metadata, or when User-Agent cannot be set from JavaScript (browsers, embedded web views) and you need to identify the client via a custom header instead:

createFreelo({
  auth: { type: 'bearer', token: '...' },
  userAgent: 'YourApp/1.0',
  headers: {
    'X-Custom-Header': 'custom-value',
  },
});

Per-request headers override these defaults. The built-in Authorization and User-Agent headers always win — use auth and userAgent to change them.

Multi-Tenant / Per-Request Auth

Create multiple client instances for different users:

import { createFreelo, getProjects } from '@freeloapp/js-sdk';

// Basic Auth client
const basicClient = createFreelo({
  auth: { type: 'basic', email: '[email protected]', apiKey: 'admin-key' },
  userAgent: 'MyApp/1.0',
});

// Bearer token client
const tokenClient = createFreelo({
  auth: { type: 'bearer', token: 'your-jwt-token' },
  userAgent: 'MyApp/1.0',
});

// OAuth client
const oauthClient = createFreelo({
  auth: { type: 'oauth', accessToken: '...', refreshToken: '...', clientId: '...' },
  userAgent: 'MyApp/1.0',
});

// Use default client (last one created)
const { data: projects } = await getProjects();

// Or pass client explicitly
const { data: adminProjects } = await getProjects({ client: basicClient });

API Reference

All SDK functions are auto-generated from the Freelo OpenAPI spec. Each function maps directly to an API endpoint.

Projects

import {
  getProjects,
  getAllProjects,
  getProject,
  createProject,
  deleteProject,
  archiveProject,
  activateProject,
  getProjectWorkers,
} from '@freeloapp/js-sdk';

// List own active projects
const { data: projects } = await getProjects();

// List all projects (paginated)
const { data } = await getAllProjects({ query: { p: 0 } });

// Get project by ID
const { data: project } = await getProject({ path: { project_id: 42 } });

// Create project
const { data: newProject } = await createProject({
  body: { name: 'New Project', currency: 'CZK' },
});

// Delete / archive / activate
await deleteProject({ path: { project_id: 42 } });
await archiveProject({ path: { project_id: 42 } });
await activateProject({ path: { project_id: 42 } });

Tasks

import {
  getAllTasks,
  getTask,
  createTask,
  editTask,
  finishTask,
  deleteTask,
  moveTask,
} from '@freeloapp/js-sdk';

// List all tasks (paginated)
const { data } = await getAllTasks({ query: { p: 0 } });

// Get task by ID
const { data: task } = await getTask({ path: { task_id: 99 } });

// Create task in a tasklist
const { data: newTask } = await createTask({
  path: { tasklist_id: 5 },
  body: { name: 'New Task', worker_ids: [1] },
});

// Edit task (name, description, labels, worker, due dates, priority, tracking users)
await editTask({
  path: { task_id: 99 },
  body: {
    name: 'Updated Name',
    description: '<p>Rewritten task body</p>', // overwrites the whole description
  },
});

// Finish / delete / move
await finishTask({ path: { task_id: 99 } });
await deleteTask({ path: { task_id: 99 } });
await moveTask({
  path: { task_id: 99 },
  body: { tasklist_id: 10 },
});

Task Labels

import {
  findAvailableTaskLabels,
  createTaskLabels,
  addTaskLabelsToTask,
  removeTaskLabelsFromTask,
  mergeTaskLabels,
  getTaskLabelColors,
} from '@freeloapp/js-sdk';

// All labels available to the caller, optionally narrowed to one project
const { data: all } = await findAvailableTaskLabels();
const { data: inProject } = await findAvailableTaskLabels({
  query: { project_id: 42 },
});

// Fetch-or-create — an existing label with the same name AND color is reused
await createTaskLabels({ body: { labels: [{ name: 'bug', color: '#ff0000' }] } });

// Attach / detach on a task (by uuid, or by name + color)
await addTaskLabelsToTask({
  path: { task_id: 99 },
  body: { labels: [{ name: 'bug', color: '#ff0000' }] },
});
await removeTaskLabelsFromTask({
  path: { task_id: 99 },
  body: { labels: [{ uuid: 'aaaaaaaa-...' }] },
});

// Merge labels — every task carrying a source label ends up with the target label
await mergeTaskLabels({
  body: {
    from_uuids: ['aaaaaaaa-...', 'bbbbbbbb-...'],
    to_uuid: 'cccccccc-...',
  },
});

Merge notes: the target's name and color come from the existing to_uuid label, all UUIDs must be owned by the caller (otherwise 404), the replacement applies only to tasks in projects where the caller is a commander, and the source label definitions are detached from tasks but not deleted.

Notifying yourself about your own actions

By default the API omits the action's author from the notification recipients. Pass notify_author: true to keep yourself in the loop — useful for automations running under your own token. Supported by createTask (and its subtasks), editTask, activateTask, finishTask, editTaskcheck, finishTaskcheck, createComment, editComment and editNote.

await createComment({
  path: { task_id: 99 },
  body: { content: 'Deployed to production', notify_author: true },
});

It only takes effect if you are otherwise a subscriber, worker or tracking user of the target.

Tasklists

import {
  getAllTasklists,
  getTasklist,
  createTasklist,
} from '@freeloapp/js-sdk';

const { data } = await getAllTasklists();
const { data: tasklist } = await getTasklist({ path: { tasklist_id: 5 } });
const { data: newTasklist } = await createTasklist({
  path: { project_id: 42 },
  body: { name: 'New Tasklist' },
});

Comments

import { getAllComments, createComment, editComment } from '@freeloapp/js-sdk';

const { data } = await getAllComments({ path: { task_id: 99 } });
const { data: comment } = await createComment({
  path: { task_id: 99 },
  body: { content: 'Hello!' },
});

Time Tracking

import { startTimeTracking, stopTimeTracking, editTimeTracking } from '@freeloapp/js-sdk';

await startTimeTracking({
  path: { task_id: 99 },
  body: { note: 'Working on feature' },
});

await stopTimeTracking({ path: { task_id: 99 } });
import { search } from '@freeloapp/js-sdk';

const { data: results } = await search({
  body: { search: 'bug fix' },
});

Pagination

The SDK provides utilities for handling paginated responses:

import { fetchAllPages, iteratePages, createPaginator, getAllProjects } from '@freeloapp/js-sdk';

// Iterate through all items one by one
for await (const project of iteratePages(
  (page) => getAllProjects({ query: { p: page } }),
  (response) => response.data ?? []
)) {
  console.log(project.name);
}

// Fetch all pages at once
const allProjects = await fetchAllPages(
  (page) => getAllProjects({ query: { p: page } }),
  (response) => response.data ?? []
);

// Create a reusable paginator
const paginator = createPaginator(
  (page) => getAllProjects({ query: { p: page } }),
  (response) => response.data ?? []
);
const all = await paginator.fetchAll();

Error Handling

import { getProject, isFreeloError, isRateLimited, isUnauthorized, isNotFound } from '@freeloapp/js-sdk';

const { data, error, response } = await getProject({ path: { project_id: 123 } });

if (error) {
  if (isRateLimited(error)) {
    console.log(`Rate limited, retry after ${response?.headers.get('Retry-After') ?? '?'} s`);
  } else if (isUnauthorized(error)) {
    console.log('Invalid credentials');
  } else if (isNotFound(error)) {
    console.log('Project not found');
  } else if (isFreeloError(error)) {
    console.log('API error:', error.message);
  }
} else {
  console.log('Project:', data);
}

Rate Limiting

The API rate-limits requests per Freelo user (token bucket). The limit is shared by all applications and tokens of the same user, and the exact numbers are configurable and may change — don't hardcode them.

  • When exceeded, you'll receive a 429 status code with a Retry-After header (seconds). Wait at least that long before retrying; use exponential backoff on repeated 429s. The SDK logs a warning automatically.
  • Responses also carry informational IETF RateLimit headers, e.g. RateLimit-Policy: "default";q=60;w=20 (quota of q requests per w seconds) and RateLimit: "default";r=42;t=5 (r requests remaining, full refill in t seconds). Retry-After is authoritative.

Utility Functions

Date Utilities

import { dateToApi, dateFromApi, today, daysFromNow } from '@freeloapp/js-sdk';

dateToApi(new Date());       // "2024-01-15"
dateFromApi('2024-01-15');   // Date object
today();                     // Today in API format
daysFromNow(7);              // 7 days from now

Currency Utilities

import { currencyToApi, currencyFromApi, formatCurrency } from '@freeloapp/js-sdk';

currencyToApi(123.45);              // "12345"
currencyFromApi("12345");           // 123.45
formatCurrency(123.45, 'CZK');     // "123,45 Kc"

Updating SDK When API Changes

The SDK is generated from the online OpenAPI spec at https://api.freelo.io/docs/v1/freelo-api.yaml.

npm run generate          # Re-generate from latest spec
npm run generate:check    # Verify generated code matches committed version

CI pipeline checks weekly for spec changes.

TypeScript Support

All types are auto-generated and fully typed:

import type { ProjectFull, TaskFull, UserBasic, TaskCreate } from '@freeloapp/js-sdk';

Migration from v1

v2 replaces the class-based API with tree-shakeable functions:

// v1 (class-based)
import { Freelo } from '@freeloapp/js-sdk';
const freelo = new Freelo({ email, apiKey, userAgent });
const projects = await freelo.projects.list();

// v2 (function-based, tree-shakeable)
import { createFreelo, getProjects } from '@freeloapp/js-sdk';
createFreelo({ auth: { type: 'basic', email, apiKey }, userAgent });
const { data: projects } = await getProjects();

Contributing

Contributions are welcome! Please read our contributing guidelines before submitting a pull request.

License

MIT

版本与发布记录

@freeloapp/js-sdk 共发布 17 个版本,首次发布于 2026/02/28;过去 12 个月发布了 17 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
2.7.5latest2026/11/0312 天前
2.7.42026/10/2125 天前
2.7.32026/09/231 个月前
2.7.22026/09/211 个月前
2.7.12026/08/282 个月前
2.7.02026/08/282 个月前

关于 @freeloapp/js-sdk 的常见问题

@freeloapp/js-sdk 是什么?

Official JavaScript/TypeScript SDK for Freelo.io API

如何安装 @freeloapp/js-sdk?

在项目目录运行 npm install @freeloapp/js-sdk,或使用 pnpm add / yarn add / bun add。

@freeloapp/js-sdk 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@freeloapp/js-sdk 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@freeloapp/js-sdk 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@freeloapp/js-sdk 可以商用吗?

@freeloapp/js-sdk 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@freeloapp/js-sdk 的下载量有多少?

根据 npm 官方下载统计,@freeloapp/js-sdk 在 2026/10/16 – 2026/11/14 这 30 天内被下载 2,168 次,日均约 72 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@freeloapp/js-sdk 还在维护吗?

最新版本 2.7.5 发布于 2026/11/03(12 天前);至今共发布 17 个版本,其中过去 12 个月发布了 17 个;首次发布于 2026/02/28。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。