@mi-pase/sdk

中文文档
0.2.3发布于 2026/11/02MITTypeScriptESM + CJS零依赖构建来源已验证

Official Node.js / TypeScript SDK for the mi-pase API (projects, enrollments, rewards, templates, and validators).

原始使用文档
文档目录14 节
  1. Installation
  2. Quick start
  3. Client options
  4. API versioning
  5. Resources
  6. Projects
  7. Enrollments
  8. Rewards
  9. Templates
  10. Validators
  11. Notifications
  12. Health
  13. Error handling
  14. Requirements

@mi-pase/sdk

Official Node.js / TypeScript SDK for the mi-pase API — manage projects, enrollments, rewards, pass templates, and validators from your backend.

  • Zero runtime dependencies, built on the native fetch.
  • Full TypeScript types for every request and response.
  • A fluent, chainable API: client.project(id).enrollment(id).setCustomFields(...).
  • Ships ESM and CJS builds.

Installation

npm install @mi-pase/sdk

That's it — the package is published to the public npm registry, no authentication required.

Installing from GitHub Packages instead

@mi-pase/sdk is also published to GitHub Packages on every release. To use that registry instead, add this to your project's .npmrc:

@mi-pase:registry=https://npm.pkg.github.com/

Then authenticate npm with a GitHub personal access token that has read:packages scope, either via npm login --scope=@mi-pase --registry=https://npm.pkg.github.com or by adding to .npmrc:

//npm.pkg.github.com/:_authToken=YOUR_GITHUB_TOKEN

Quick start

import { MiPaseClient } from "@mi-pase/sdk";

const client = new MiPaseClient({
  // A Clerk Organization API key, created from your mi-pase organization's
  // API Keys settings panel. Sent as `Authorization: Bearer <apiKey>`.
  apiKey: process.env.MI_PASE_API_KEY!,
});

const project = await client.projects.create({
  domain: "acme",
  name: "Summer Fest 2026",
});

const enrollment = await client.project(project._id).enrollments.create({
  domain: "acme",
  name: "Jane Doe",
  email: "[email protected]",
});

// Chain from a project straight into one of its enrollments:
await client
  .project(project._id)
  .enrollment(enrollment._id)
  .setCustomFields({ seat: "12A" }, { override: true });

Client options

new MiPaseClient({
  apiKey: "...",           // required — Clerk Organization API key
  baseUrl: "...",          // default: "https://app.mi-pase.ar"
  timeoutMs: 30_000,       // per-request timeout
  maxRetries: 2,           // retries for GET/PUT/DELETE on network/timeout/429/5xx errors
  fetch: customFetch,      // override the fetch implementation
});

POST requests are never retried automatically (they're not idempotent by default — e.g. creating a project twice would create two projects). Pass { idempotent: true } as a per-call option where the underlying resource method exposes it if you know a specific POST is safe to retry.

API versioning

The mi-pase API is currently v1 only; every method shown below talks to v1 endpoints. The client accepts a reserved apiVersion option ("v1" today) so that when v2 endpoints ship, this SDK can add a v2 surface without a breaking change to the v1 one.

Resources

Projects

await client.projects.list({ domain, kind });
await client.projects.create({ domain, name, ... });

await client.project(id).get();
await client.project(id).update({ name: "New name" });
await client.project(id).delete();      // only finalized projects can be deleted
await client.project(id).duplicate();

Enrollments

await client.enrollments.list({ project, person, domain });
await client.enrollments.create({ domain, project_id, name, email, phone, customFields });
await client.enrollments.bulkCreate({ domain, project_id, rows: [...] });
await client.enrollments.copyToProject({ sourceProjectId, targetProjectId, domain });

await client.enrollment(id).get();
await client.enrollment(id).update({ customFields });  // full replace, matches the raw API
await client.enrollment(id).delete();                  // unenrolls the person (and revokes their pass)
await client.enrollment(id).revoke();                  // revokes the issued pass; person stays enrolled
await client.enrollment(id).setCustomFields({ seat: "12A" });                     // merges with existing fields (fetches first)
await client.enrollment(id).setCustomFields({ seat: "12A" }, { override: true }); // full replace, no extra fetch

Scoped to a project, project_id is filled in for you:

await client.project(projectId).enrollments.list();
await client.project(projectId).enrollments.create({ domain, name, email });
await client.project(projectId).enrollments.bulkCreate({ domain, rows: [...] });
await client.project(projectId).enrollment(enrollmentId).setCustomFields({...});

Rewards

Rewards only exist within a project — there's no top-level /rewards endpoint.

await client.project(projectId).rewards.list();
await client.project(projectId).rewards.create({ name, description, pointsRequired });
await client.project(projectId).rewards.update(rewardId, { pointsRequired: 200 });
await client.project(projectId).rewards.delete(rewardId);

Templates

await client.templates.list({ domain });
await client.templates.create({ domain, name, type: "event" });

await client.template(id).get();
await client.template(id).update({ name: "New name" });
await client.template(id).delete();
await client.template(id).duplicate();
await client.template(id).getConfig();              // Apple + Google wallet design, or undefined if unsynced
await client.template(id).saveConfig({ apple, google });

Validators

await client.validators.list({ domain });
await client.validators.create({ domain, name, project, scanMode: "both" });
await client.validators.recordsForEnrollments([enrollmentId1, enrollmentId2]);

await client.validator(id).get();
await client.validator(id).update({ scanMode: "nfc" });
await client.validator(id).delete();
await client.validator(id).scan({ scannedValue, scanMode: "camera" });
await client.validator(id).records({ limit: 50, skip: 0, search: "" });

scan() can return { matches: [...] } instead of a validation record when the scanned value resolves to more than one enrollment — use the exported isScanAmbiguous() type guard and re-scan with enrollmentId set:

import { isScanAmbiguous } from "@mi-pase/sdk";

const result = await client.validator(id).scan({ scannedValue, scanMode: "camera" });
if (isScanAmbiguous(result)) {
  // prompt the operator to pick one of result.matches
} else {
  // result is a ValidationRecord
}

Notifications

Wallet push notifications to a project's active passes. send targets the whole project by default; narrow it with enrollmentIds (specific enrollments) and/or an issuedFrom/issuedTo issued-date window. message is shared across both wallets, with optional per-platform overrides — googleMessage may carry a single <a href> anchor that Google Wallet renders as a tappable link (Apple pushes have no link support).

await client.notifications.send({
  domain,
  project: projectId,
  message: "Doors open at 7pm.",
  // optional per-platform overrides:
  appleMessage: "Doors open at 7pm.",
  googleMessage: 'Doors open at 7pm. <a href="https://acme.example/map">View map</a>',
  // optional filters:
  enrollmentIds: [enrollmentId1, enrollmentId2],
  issuedFrom: "2026-10-01",
  issuedTo: "2026-10-31",
});

await client.notifications.list({ domain });        // sent-notification history for a domain
await client.notifications.listByPasses([passId1]); // history touching specific passes
await client.notifications.preview({ domain, project: projectId, enrollmentIds }); // { count, cooldownUntil }

The API caps sends at one notification per project per 24h: a send inside that window throws a MiPaseApiError with status 429. Call preview first to get the would-be target count and the cooldownUntil timestamp (ISO string, or null when a send is allowed now) without consuming the cooldown.

Health

await client.health(); // { status: "ok", timestamp: "..." }

Error handling

Every non-2xx response throws MiPaseApiError:

import { MiPaseApiError } from "@mi-pase/sdk";

try {
  await client.project("does-not-exist").get();
} catch (error) {
  if (error instanceof MiPaseApiError) {
    console.error(error.status, error.message, error.body);
  }
  throw error;
}

Network failures throw MiPaseNetworkError; client-side timeouts throw MiPaseTimeoutError.

Requirements

Node.js 18 or later (for global fetch), or pass your own fetch implementation via options.fetch.

版本与发布记录

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

版本发布日期距今
0.2.3latest2026/11/021 天前
0.2.22026/11/021 天前
0.2.12026/10/2113 天前
0.2.02026/10/2113 天前
0.1.32026/09/141 个月前
0.1.22026/08/272 个月前

关于 @mi-pase/sdk 的常见问题

@mi-pase/sdk 是什么?

mi-pase API(项目、注册、奖励、模板和验证器)的官方Node.js / TypeScript SDK。

如何安装 @mi-pase/sdk?

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

@mi-pase/sdk 支持 TypeScript 吗?

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

@mi-pase/sdk 支持 ESM 还是 CommonJS?

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

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

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

@mi-pase/sdk 可以商用吗?

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

@mi-pase/sdk 还在维护吗?

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