Skip to content

API reference

Complete reference for every Lovrabet OpenAPI endpoint, covering both SDK usage and the underlying HTTP specification.

Basics

Base URL

EnvironmentDomain
Productionhttps://runtime.lovrabet.com

Request conventions

  • Method: all endpoints use POST
  • Content type: application/json
  • Character encoding: UTF-8
  • Timeout: we recommend 30 seconds

Authentication

Every request must carry these authentication headers:

HeaderDescriptionExample
X-Time-StampTimestamp (milliseconds)1758903130713
X-App-CodeApp codeapp-c2dd52a2
X-Dataset-CodeDataset code (required by some endpoints)0fefba76fe29...ff
X-TokenSigned tokenjdqqGtzecF2I6FIW...

TIP

Use the SDK The SDK adds all required headers automatically — nothing to manage by hand.

OpenAPI endpoints

Full endpoint list

Here is everything Lovrabet OpenAPI offers:

EndpointHTTP methodFull URL pathSDK methodDescription
List recordsPOST/openapi/data/get-listfilter(params)Paginated query with filtering and sorting
Get a single recordPOST/openapi/data/get-onegetOne(id)Fetch a single record by ID
Create a recordPOST/openapi/data/createcreate(data)Create a new record
Update a recordPOST/openapi/data/updateupdate(id, data)Update an existing record
Execute SQLPOST/api/custom/executeSqlsql.execute(options)Run a custom SQL query

INFO

Feature limitations OpenAPI does not currently support delete(), getSelectOptions(), or excelExport(). For those operations, use WebAPI mode, which authenticates with Cookies.

Request body conventions

Every OpenAPI endpoint takes a request body with this structure:

TypeScript
{
  appCode: string;           // 应用编码(所有接口必需)
  datasetCode: string;       // 数据集编码(所有数据操作接口都需要)
  paramMap: {                // 业务参数对象
    // 具体参数根据不同接口而定
  }
}

How credentials are passed:

  • OpenAPI credentials are not placed in the request body — they travel in HTTP headers:

    • X-Time-Stamp: timestamp
    • X-App-Code: app code
    • X-Dataset-Code: dataset code
    • X-Token: signed token
  • The SDK sets these headers automatically; you never add them by hand

Endpoint parameters in detail

1. List records (getList)

URL: POST /openapi/data/get-list

Request body:

TypeScript
{
  appCode: string;
  datasetCode: string;
  paramMap: {
    currentPage?: number;     // 当前页码,从 1 开始
    pageSize?: number;        // 每页条数
    ytSortList?: SortList;    // 排序配置(通过 SDK 的 sortList 参数自动添加)
    [key: string]: any;       // 其他查询条件(根据数据集字段)
  }
}

Parameters:

ParameterTypeRequiredDefaultDescription
appCodestring✅ Yes-App code
datasetCodestring✅ Yes-Dataset code
paramMap.currentPagenumber❌ No1Current page number, starting at 1
paramMap.pageSizenumber❌ No20Records per page, up to 100
paramMap.ytSortListSortList❌ No-Sort configuration array, generated automatically from the SDK's sortList argument
paramMap.[field_name]any❌ No-Additional query conditions, based on the dataset's actual fields

Query condition examples:

Depending on your dataset's fields, these patterns are available:

TypeScript
{
  // 精确匹配
  status: "active",
  customer_type: "vip",

  // 模糊查询(字段名加 _like 后缀)
  name_like: "张",

  // 范围查询(字段名加 _start/_end 后缀)
  create_time_start: "2024-01-01",
  create_time_end: "2024-12-31",
  amount_start: 1000,
  amount_end: 5000,

  // IN 查询(字段名加 _in 后缀,值为数组)
  status_in: ["active", "pending"],

  // IS NULL 查询(字段名加 _is_null 后缀)
  deleted_at_is_null: true,
}

Sorting (ytSortList):

OpenAPI sorts through the ytSortList field, which you pass via the SDK's sortList argument:

TypeScript
import { SortOrder } from "@lovrabet/sdk";

// SDK 调用方式(推荐)
const result = await client.models.users.filter(
  { currentPage: 1, pageSize: 20 },
  [
    { priority: SortOrder.DESC }, // 优先级降序
    { createTime: SortOrder.DESC }, // 创建时间降序
    { name: SortOrder.ASC }, // 名称升序
  ]
);

// SDK 会自动将 sortList 转换为 ytSortList 字段放到 paramMap 中
// 实际请求体: { appCode, datasetCode, paramMap: { ytSortList: [...] } }

TIP

About sorting

  • OpenAPI supports sorting only through the ytSortList field
  • With the SDK, pass the sort configuration as the second argument (sortList) to getList()
  • The SDK converts sortList into the ytSortList format automatically

Response (data field):

TypeScript
{
  paging: {
    pageSize: number; // 每页记录数
    totalCount: number; // 总记录数
    currentPage: number; // 当前页码
  }
  tableData: Array<T>; // 数据列表数组
  tableColumns: Array<{
    title: string; // 列标题
    dataIndex: string; // 字段名
  }>;
}

2. Get a single record (getOne)

URL: POST /openapi/data/get-one

Request body:

TypeScript
{
  appCode: string;
  datasetCode: string;
  paramMap: {
    id: string | number; // 记录 ID
  }
}

Parameters:

ParameterTypeRequiredDescription
appCodestring✅ YesApp code
datasetCodestring✅ YesDataset code
paramMap.idstring/number✅ YesID of the record to fetch

Response (data field):

TypeScript
{
  id: string | number;     // 记录 ID
  [key: string]: any;      // 其他字段根据数据集定义
}

3. Create a record (create)

URL: POST /openapi/data/create

Request body:

TypeScript
{
  appCode: string;
  datasetCode: string;
  paramMap: {
    [key: string]: any;   // 要创建的数据字段
  }
}

Parameters:

ParameterTypeRequiredDescription
appCodestring✅ YesApp code
datasetCodestring✅ YesDataset code
paramMap.[field_name]anyDepends on the field definitionData to create; which fields exist and which are required follow the dataset definition

Notes:

  • System fields (id, gmt_create, gmt_modified) are generated automatically — don't pass them
  • Required fields must be provided, or the API returns a parameter error
  • Field types must match the dataset definition

Response (data field):

TypeScript
{
  id: string | number;     // 新创建记录的 ID
  [key: string]: any;      // 完整的记录数据(包括自动生成的字段)
}

4. Update a record (update)

URL: POST /openapi/data/update

Request body:

TypeScript
{
  appCode: string;
  datasetCode: string;
  paramMap: {
    id: string | number;  // 要更新的记录 ID
    [key: string]: any;   // 要更新的字段
  }
}

Parameters:

ParameterTypeRequiredDescription
appCodestring✅ YesApp code
datasetCodestring✅ YesDataset code
paramMap.idstring/number✅ YesID of the record to update
paramMap.[field_name]any❌ NoFields to update — pass only the ones you want to change (partial update)

Notes:

  • Partial updates are supported: pass only the fields you want to change
  • gmt_modified is updated automatically
  • id and gmt_create cannot be modified

Response (data field):

TypeScript
{
  id: string | number;     // 记录 ID
  [key: string]: any;      // 完整的更新后记录数据
}

Response structure

Common response format

Every response contains the following top-level fields (returned by the platform gateway):

TypeScript
{
  success: boolean;      // 请求是否成功
  msg: string;           // 响应消息
  data: any;             // 响应数据
  errorCode?: number;    // 错误码(失败时返回)
  errorMsg?: string;     // 错误信息(失败时返回)
}

Fixed fields in the getList response

The data field of getList always contains the following three fields (the SDK returns the same structure as the platform):

TypeScript
{
  paging: {
    // 【固定字段】分页信息
    pageSize: number; // 每页记录数
    totalCount: number; // 总记录数
    currentPage: number; // 当前页码
  }
  tableData: Array<T>; // 【固定字段】数据列表数组
  tableColumns: Array<{
    // 【固定字段】表格列定义
    title: string; // 列标题
    dataIndex: string; // 字段名
  }>;
}

Using the SDK

Creating a client

TypeScript
import { createClient } from "@lovrabet/sdk";

const client = createClient({
  appCode: "your-app-code",
  accessKey: process.env.LOVRABET_ACCESS_KEY,
  models: {
    users: {
      tableName: "users",
      datasetCode: "your-dataset-code",
    },
  },
});

SDK methods

Every model instance provides these methods:

MethodDescriptionEndpoint
getList(params?)List records/openapi/data/get-list
getOne(id)Get a single record/openapi/data/get-one
create(data)Create a record/openapi/data/create
update(id, data)Update a record/openapi/data/update

Executing SQL

OpenAPI mode also runs custom SQL queries — handy for complex reports, multi-table joins, and similar scenarios.

Basic usage

TypeScript
// 执行 SQL 查询
const result = await client.sql.execute({
  sqlCode: 'your-sql-code',  // SQL 代码标识
  params: {                  // 可选:SQL 参数
    startDate: '2024-01-01',
    endDate: '2024-12-31'
  }
});

// 检查执行结果
if (result.execSuccess) {
  console.log('查询结果:', result.execResult);
} else {
  console.error('执行失败:', result.execError);
}

Type-safe calls

TypeScript
interface MonthlyStats {
  customer_name: string;
  total_amount: number;
  order_count: number;
}

const result = await client.sql.execute<MonthlyStats>({
  sqlCode: 'monthly-sales-stats',
  params: { year: 2024 }
});

if (result.execSuccess && result.execResult) {
  result.execResult.forEach(stat => {
    console.log(`${stat.customer_name}: ${stat.total_amount}元`);
  });
}

Use the sqlSafe wrapper to simplify error handling:

TypeScript
import { sqlSafe } from "@lovrabet/sdk";

const { data, error } = await sqlSafe(() =>
  client.sql.execute({
    sqlCode: 'customer-orders',
    params: { customerId: '123' }
  })
);

if (error) {
  console.error('查询失败:', error.message);
  return;
}

// data 直接是查询结果数组
console.log('订单数量:', data.length);

Return value structure

TypeScript
interface SqlExecuteResult<T> {
  execSuccess: boolean;  // SQL 执行是否成功
  execResult?: T[];      // 查询结果数组(成功时)
  execError?: string;    // 错误信息(失败时)
}

TIP

When to use SQL

  • Complex multi-table joins
  • Aggregated statistical reports
  • Conditions too complex to express with filter
  • High-performance queries that need tuning

INFO

SQL management Custom SQL must be created and configured on the Lovrabet platform in advance. You can manage SQL queries with MCP tools or through the platform UI.

API walkthrough

1. List records (getList)

Query dataset records with pagination, filtering, and sorting.

SDK usage

Basic query:

TypeScript
const response = await client.models.users.filter({
  currentPage: 1,
  pageSize: 20,
});

// 解构响应
const { paging, tableData, tableColumns } = response;

console.log("总条数:", paging.totalCount);
console.log("当前页:", paging.currentPage);
console.log("数据:", tableData);
console.log("列定义:", tableColumns);

Filtered query:

TypeScript
const response = await client.models.users.filter({
  currentPage: 1,
  pageSize: 20,
  // 查询条件(根据数据集实际字段)
  status: "active",
  customer_type: "vip",
});

Walk all pages:

TypeScript
async function getAllUsers() {
  const allUsers = [];
  let currentPage = 1;
  const pageSize = 50;

  while (true) {
    const { paging, tableData } = await client.models.users.filter({
      currentPage,
      pageSize,
    });

    allUsers.push(...tableData);

    // 判断是否还有更多数据
    if (currentPage * pageSize >= paging.totalCount) {
      break;
    }

    currentPage++;

    // 避免请求过快
    await new Promise((resolve) => setTimeout(resolve, 100));
  }

  return allUsers;
}

HTTP specification

Endpoint: POST /openapi/data/get-list

Headers:

Plain
X-Time-Stamp: {timestamp}
X-App-Code: {appCode}
X-Dataset-Code: {datasetCode}
X-Token: {token}

Request body:

JSON
{
  "appCode": "app-c2dd52a2",
  "datasetCode": "0fefba76fe29440194841f4825df53ff",
  "paramMap": {
    "pageSize": 10,
    "currentPage": 1,
    "status": "active",
    "customer_type": "vip",
    "create_time_start": "2024-01-01",
    "create_time_end": "2024-12-31"
  }
}

Request body with sorting:

JSON
{
  "appCode": "app-c2dd52a2",
  "datasetCode": "0fefba76fe29440194841f4825df53ff",
  "paramMap": {
    "pageSize": 10,
    "currentPage": 1,
    "ytSortList": [{ "gmt_create": "desc" }, { "id": "asc" }],
    "status": "active"
  }
}

Response structure:

The data field always contains three fixed fields:

  • paging - pagination info (fixed field)
  • tableData - the record list (fixed field)
  • tableColumns - column definitions (fixed field)

Response example:

JSON
{
  "success": true,
  "msg": "查询成功",
  "data": {
    "paging": {
      "pageSize": 10,
      "totalCount": 156,
      "currentPage": 1
    },
    "tableData": [
      {
        "id": "123",
        "customer_name": "示例客户",
        "customer_type": "vip",
        "contact_person": "张三",
        "phone": "13800138000",
        "email": "example@example.com",
        "address": "北京市朝阳区",
        "status": "active",
        "credit_level": "A",
        "gmt_create": "2024-01-15 10:30:00",
        "gmt_modified": "2024-03-20 14:25:00"
      }
    ],
    "tableColumns": [
      {
        "title": "客户名称",
        "dataIndex": "customer_name"
      },
      {
        "title": "客户类型",
        "dataIndex": "customer_type"
      },
      {
        "title": "联系人",
        "dataIndex": "contact_person"
      }
    ]
  }
}

Query parameters in detail

Pagination:

ParameterTypeRequiredDefaultDescription
pageSizenumberNo10Records per page, up to 100
currentPagenumberNo1Current page number, starting at 1

Sorting:

ParameterTypeRequiredDescription
ytSortListArrayNoSort configuration array in the format [{ "field_name": "asc/desc" }]

TIP

Passing sorts With the SDK, pass sortList as the second argument to getList(); the SDK converts it into the ytSortList field. See the "Sorting" section above.

Filters:

Filter parameters depend on your dataset's fields. Common patterns:

TypeScript
{
  // 精确匹配
  field_name: "value",

  // 模糊查询(部分字段支持)
  field_name_like: "value",

  // 范围查询
  field_name_start: "value1",
  field_name_end: "value2",

  // IN 查询(部分字段支持)
  field_name_in: ["value1", "value2"],

  // NULL 判断(部分字段支持)
  field_name_is_null: true,
}

2. Get a single record (getOne)

Fetch one record's full details by its unique ID.

SDK usage

TypeScript
// 查询单条数据
const user = await client.models.users.getOne("user-id");

console.log(user);
// 返回单个对象,包含该记录的所有字段

// 或者使用对象参数
const user = await client.models.users.getOne({
  id: 14,
});

HTTP specification

Endpoint: POST /openapi/data/get-one

Headers:

Plain
X-Time-Stamp: {timestamp}
X-App-Code: {appCode}
X-Dataset-Code: {datasetCode}
X-Token: {token}

Request body:

JSON
{
  "appCode": "app-c2dd52a2",
  "datasetCode": "0fefba76fe29440194841f4825df53ff",
  "paramMap": {
    "id": 14
  }
}

Response example:

JSON
{
  "success": true,
  "msg": "查询成功",
  "data": {
    "id": 14,
    "customer_name": "示例客户",
    "customer_type": "vip",
    "contact_person": "张三",
    "phone": "13800138000",
    "email": "example@example.com",
    "address": "北京市朝阳区XX路XX号",
    "status": "active",
    "credit_level": "A",
    "credit_amount": 100000.0,
    "used_amount": 35000.0,
    "available_amount": 65000.0,
    "contract_start": "2024-01-01",
    "contract_end": "2024-12-31",
    "sales_person": "王经理",
    "department": "华北销售部",
    "gmt_create": "2024-01-15 10:30:00",
    "gmt_modified": "2024-03-20 14:25:00",
    "remark": "重要VIP客户,需重点维护"
  }
}

3. Create a record (create)

Create a new record.

SDK usage

TypeScript
// 新增客户信息
const newCustomer = await client.models.users.create({
  customer_name: "新客户公司",
  customer_type: "enterprise",
  contact_person: "李经理",
  phone: "13900139000",
  email: "contact@newcustomer.com",
  address: "上海市浦东新区",
  status: "active",
  credit_level: "B",
});

console.log("新客户 ID:", newCustomer.id);
console.log("创建时间:", newCustomer.gmt_create);

HTTP specification

Endpoint: POST /openapi/data/create

Headers:

Plain
X-Time-Stamp: {timestamp}
X-App-Code: {appCode}
X-Dataset-Code: {datasetCode}
X-Token: {token}

Request body:

JSON
{
  "appCode": "app-c2dd52a2",
  "datasetCode": "0fefba76fe29440194841f4825df53ff",
  "paramMap": {
    "customer_name": "新客户公司",
    "customer_type": "enterprise",
    "contact_person": "李经理",
    "phone": "13900139000",
    "email": "contact@newcustomer.com",
    "address": "上海市浦东新区",
    "status": "active",
    "credit_level": "B"
  }
}

Response example:

JSON
{
  "success": true,
  "msg": "创建成功",
  "data": {
    "id": 158,
    "customer_name": "新客户公司",
    "customer_type": "enterprise",
    "contact_person": "李经理",
    "phone": "13900139000",
    "email": "contact@newcustomer.com",
    "address": "上海市浦东新区",
    "status": "active",
    "credit_level": "B",
    "credit_amount": 0.0,
    "used_amount": 0.0,
    "available_amount": 0.0,
    "gmt_create": "2025-10-10 15:30:00",
    "gmt_modified": "2025-10-10 15:30:00"
  }
}

4. Update a record (update)

Update some or all fields of an existing record by ID.

SDK usage

TypeScript
// 更新客户信息
const updatedCustomer = await client.models.users.update(158, {
  customer_type: "vip",
  credit_level: "A",
  credit_amount: 200000.0,
  remark: "升级为 VIP 客户,提升信用额度",
});

console.log("更新后的客户类型:", updatedCustomer.customer_type);
console.log("更新时间:", updatedCustomer.gmt_modified);

Batch update example:

TypeScript
// 批量更新多个客户的状态
const customerIds = [101, 102, 103];

const updateResults = await Promise.all(
  customerIds.map((id) =>
    client.models.users.update(id, {
      status: "inactive",
      remark: "批量停用",
    })
  )
);

console.log(`成功更新 ${updateResults.length} 个客户`);

HTTP specification

Endpoint: POST /openapi/data/update

Headers:

Plain
X-Time-Stamp: {timestamp}
X-App-Code: {appCode}
X-Dataset-Code: {datasetCode}
X-Token: {token}

Request body:

JSON
{
  "appCode": "app-c2dd52a2",
  "datasetCode": "0fefba76fe29440194841f4825df53ff",
  "paramMap": {
    "id": 158,
    "customer_type": "vip",
    "credit_level": "A",
    "credit_amount": 200000.0,
    "remark": "升级为 VIP 客户,提升信用额度"
  }
}

Response example:

JSON
{
  "success": true,
  "msg": "更新成功",
  "data": {
    "id": 158,
    "customer_name": "新客户公司",
    "customer_type": "vip",
    "contact_person": "李经理",
    "phone": "13900139000",
    "email": "contact@newcustomer.com",
    "address": "上海市浦东新区",
    "status": "active",
    "credit_level": "A",
    "credit_amount": 200000.0,
    "used_amount": 0.0,
    "available_amount": 200000.0,
    "gmt_create": "2025-10-10 15:30:00",
    "gmt_modified": "2025-10-10 16:45:00",
    "remark": "升级为 VIP 客户,提升信用额度"
  }
}

Recommended: soft delete

OpenAPI currently offers no hard-delete endpoint. To delete data, use a soft delete — update a status field via update():

TypeScript
// 推荐:使用软删除(更新状态为 deleted)
await client.models.users.update(158, {
  status: "deleted",
  deleted_at: new Date().toISOString(),
  remark: "客户要求删除数据",
});

console.log("客户已标记为删除");

Why soft delete:

  • ✅ Data stays recoverable
  • ✅ The operation history is preserved
  • ✅ Meets data compliance requirements

Error handling

Error codes

When a request fails, success is false and the response includes an error code and message.

CodeDescriptionSolution
1001Invalid parametersCheck that the request parameters are complete and correctly formatted
1002Signature verification failedCheck the Access Key, the signing algorithm, and the timestamp
1003Timestamp expiredThe token has expired (10 minutes); generate a new one
1004App not foundCheck the App Code
1005Dataset not foundCheck the Dataset Code
1006Access deniedConfirm the app has access to the dataset
2001Query timeoutOptimize the query conditions to reduce the data volume
2002Record not foundCheck the query conditions or the ID
3001Internal server errorContact technical support
4001Rate limit exceededSlow down and add throttling

Error response example

JSON
{
  "success": false,
  "msg": null,
  "data": null,
  "errorCode": 1002,
  "errorMsg": "签名验证失败,请检查 Token 生成算法"
}

SDK error handling

TypeScript
import { LovrabetError } from "@lovrabet/sdk";

try {
  const users = await client.models.users.filter();
} catch (error) {
  if (error instanceof LovrabetError) {
    console.error("错误码:", error.statusCode);
    console.error("错误信息:", error.message);
    console.error("错误详情:", error.details);

    // 根据错误码处理
    switch (error.statusCode) {
      case 1002:
        console.error("签名验证失败,请检查 Access Key");
        break;
      case 1003:
        console.error("Token 已过期,需要刷新");
        break;
      case 1006:
        console.error("无权访问该数据集");
        break;
      default:
        console.error("其他错误");
    }
  } else {
    console.error("未知错误:", error);
  }
}

Advanced usage

1. TypeScript types

TypeScript
import { createClient, ListResponse } from "@lovrabet/sdk";

// 定义数据类型
interface User {
  id: string;
  name: string;
  email: string;
  status: "active" | "inactive";
  gmt_create: string;
}

const client = createClient({
  // ... 配置
});

// 类型化查询
const response: ListResponse<User> = await client.models.users.filter();

response.tableData.forEach((user: User) => {
  console.log(user.name, user.email);
});

2. Multiple models

TypeScript
const client = createClient({
  appCode: "your-app-code",
  accessKey: process.env.LOVRABET_ACCESS_KEY,
  models: {
    users: {
      tableName: "users",
      datasetCode: "dataset-001",
    },
    orders: {
      tableName: "orders",
      datasetCode: "dataset-002",
    },
    products: {
      tableName: "products",
      datasetCode: "dataset-003",
    },
  },
});

// 使用不同的模型
const users = await client.models.users.filter();
const orders = await client.models.orders.filter();
const products = await client.models.products.filter();

3. Transforming responses

TypeScript
const response = await client.models.users.filter();

// 提取纯数据数组
const users = response.tableData;

// 提取列定义(用于动态构建表格 UI)
const columns = response.tableColumns.map((col) => ({
  title: col.title,
  field: col.dataIndex,
}));

// 分页信息
const { totalCount, currentPage, pageSize } = response.paging;
const totalPages = Math.ceil(totalCount / pageSize);

4. Request interception and logging

TypeScript
import { createClient } from "@lovrabet/sdk";

const client = createClient({
  appCode: "your-app-code",
  accessKey: process.env.LOVRABET_ACCESS_KEY,
  models: {
    /* ... */
  },
});

// 包装请求方法添加日志
const originalGetList = client.models.users.getList.bind(client.models.users);

client.models.users.getList = async function (params) {
  console.log("请求参数:", params);
  const startTime = Date.now();

  try {
    const result = await originalGetList(params);
    console.log("请求成功,耗时:", Date.now() - startTime, "ms");
    return result;
  } catch (error) {
    console.error("请求失败:", error);
    throw error;
  }
};

Performance

1. Use sensible page sizes

TypeScript
// ✅ 推荐:合理的分页大小
const response = await client.models.users.filter({
  pageSize: 20, // 10-50 条较合适
});

// ❌ 避免:单次查询过多数据
const response = await client.models.users.filter({
  pageSize: 1000, // 不推荐,影响性能
});

2. Avoid N+1 queries

TypeScript
// ❌ 不推荐:循环调用 getOne
for (const id of userIds) {
  const user = await client.models.users.getOne(id);
  // 处理 user
}

// ✅ 推荐:使用 getList 批量查询
const users = await client.models.users.filter({
  id_in: userIds, // 假设支持 IN 查询
});

3. Add caching

TypeScript
class CachedApiClient {
  private cache = new Map<string, { data: any; timestamp: number }>();
  private cacheTTL = 60000; // 1 分钟

  async getListCached(params: any) {
    const cacheKey = JSON.stringify(params);
    const cached = this.cache.get(cacheKey);

    if (cached && Date.now() - cached.timestamp < this.cacheTTL) {
      return cached.data;
    }

    const data = await client.models.users.filter(params);
    this.cache.set(cacheKey, { data, timestamp: Date.now() });

    return data;
  }
}

4. Control concurrency

TypeScript
// 限制并发数
async function batchQuery(ids: string[], concurrency = 3) {
  const results = [];

  for (let i = 0; i < ids.length; i += concurrency) {
    const batch = ids.slice(i, i + concurrency);
    const batchResults = await Promise.all(
      batch.map((id) => client.models.users.getOne(id))
    );
    results.push(...batchResults);
  }

  return results;
}

Request limits

Rate limits

LimitValue
Per minute600 requests
Per hour10,000 requests
Per day100,000 requests

Payload limits

LimitValue
Max records returned per query100
Max response body10MB
Max request body1MB

Concurrency limits

LimitValue
Max concurrent connections per app10

TIP

Performance tips

  • Manage requests with a connection pool
  • Add request queues and throttling
  • Set sensible timeouts

Raw HTTP requests (advanced)

INFO

For reference only Use the SDK. The material below is only for developers who need to work at the protocol level.

Generating the signature

TypeScript
import crypto from "crypto";

function generateToken(
  timestamp: number,
  appCode: string,
  datasetCode: string,
  accessKey: string,
  secretKey: string = "lovrabet"
): string {
  const params: Record<string, string> = {
    accessKey: accessKey,
    timeStamp: timestamp.toString(),
    appCode: appCode,
  };

  if (datasetCode) {
    params.datasetCode = datasetCode;
  }

  // 按字典序排序参数
  const sortedParams = Object.keys(params)
    .sort()
    .map((key) => `${key}=${params[key]}`)
    .join("&");

  // 计算 HMAC-SHA256
  return crypto
    .createHmac("sha256", secretKey)
    .update(sortedParams, "utf8")
    .digest("base64");
}

Raw request example

TypeScript
async function getListRaw() {
  const timestamp = Date.now();
  const appCode = "app-c2dd52a2";
  const datasetCode = "0fefba76fe29440194841f4825df53ff";
  const accessKey = "your-access-key";

  const token = generateToken(timestamp, appCode, datasetCode, accessKey);

  const response = await fetch(
    "https://runtime.lovrabet.com/openapi/data/get-list",
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-Time-Stamp": timestamp.toString(),
        "X-App-Code": appCode,
        "X-Dataset-Code": datasetCode,
        "X-Token": token,
      },
      body: JSON.stringify({
        appCode: appCode,
        datasetCode: datasetCode,
        paramMap: {
          pageSize: 10,
          currentPage: 1,
        },
      }),
    }
  );

  return await response.json();
}

Best practices

1. Errors and retries

TypeScript
async function apiCallWithRetry(
  fn: () => Promise<any>,
  maxRetries = 3,
  delay = 1000
) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (error instanceof LovrabetError) {
        // Token 过期可以重试
        if (error.statusCode === 1003 && i < maxRetries - 1) {
          await new Promise((resolve) => setTimeout(resolve, delay));
          continue;
        }
      }
      throw error;
    }
  }
}

// 使用
const users = await apiCallWithRetry(() => client.models.users.filter());

2. Logging

TypeScript
function logApiCall(
  method: string,
  params: any,
  result: any,
  duration: number
) {
  console.log({
    timestamp: new Date().toISOString(),
    method: method,
    params: params,
    success: true,
    duration: duration,
    recordCount: result.tableData?.length || 0,
  });
}

3. Monitoring and alerting

TypeScript
class ApiMonitor {
  private errorCount = 0;
  private errorThreshold = 10;

  async callWithMonitoring(fn: () => Promise<any>) {
    try {
      const result = await fn();
      this.errorCount = 0; // 重置错误计数
      return result;
    } catch (error) {
      this.errorCount++;

      if (this.errorCount >= this.errorThreshold) {
        // 触发告警
        this.sendAlert("API 错误率过高");
      }

      throw error;
    }
  }

  private sendAlert(message: string) {
    // 发送告警通知
    console.error("告警:", message);
  }
}

Need help?

If something goes wrong:

  1. Check the error codes in this document
  2. Browse GitHub Issues
  3. Contact your account manager for technical support

基于飞书知识库同步生成,内容以飞书源文档为准