API reference
Complete reference for every Lovrabet OpenAPI endpoint, covering both SDK usage and the underlying HTTP specification.
Basics
Base URL
| Environment | Domain |
|---|---|
| Production | https://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:
| Header | Description | Example |
|---|---|---|
| X-Time-Stamp | Timestamp (milliseconds) | 1758903130713 |
| X-App-Code | App code | app-c2dd52a2 |
| X-Dataset-Code | Dataset code (required by some endpoints) | 0fefba76fe29...ff |
| X-Token | Signed token | jdqqGtzecF2I6FIW... |
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:
| Endpoint | HTTP method | Full URL path | SDK method | Description |
|---|---|---|---|---|
| List records | POST | /openapi/data/get-list | filter(params) | Paginated query with filtering and sorting |
| Get a single record | POST | /openapi/data/get-one | getOne(id) | Fetch a single record by ID |
| Create a record | POST | /openapi/data/create | create(data) | Create a new record |
| Update a record | POST | /openapi/data/update | update(id, data) | Update an existing record |
| Execute SQL | POST | /api/custom/executeSql | sql.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:
{
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: timestampX-App-Code: app codeX-Dataset-Code: dataset codeX-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:
{
appCode: string;
datasetCode: string;
paramMap: {
currentPage?: number; // 当前页码,从 1 开始
pageSize?: number; // 每页条数
ytSortList?: SortList; // 排序配置(通过 SDK 的 sortList 参数自动添加)
[key: string]: any; // 其他查询条件(根据数据集字段)
}
}Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
appCode | string | ✅ Yes | - | App code |
datasetCode | string | ✅ Yes | - | Dataset code |
paramMap.currentPage | number | ❌ No | 1 | Current page number, starting at 1 |
paramMap.pageSize | number | ❌ No | 20 | Records per page, up to 100 |
paramMap.ytSortList | SortList | ❌ 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:
{
// 精确匹配
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:
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
ytSortListfield - With the SDK, pass the sort configuration as the second argument (
sortList) togetList() - The SDK converts
sortListinto theytSortListformat automatically
Response (data field):
{
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:
{
appCode: string;
datasetCode: string;
paramMap: {
id: string | number; // 记录 ID
}
}Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
appCode | string | ✅ Yes | App code |
datasetCode | string | ✅ Yes | Dataset code |
paramMap.id | string/number | ✅ Yes | ID of the record to fetch |
Response (data field):
{
id: string | number; // 记录 ID
[key: string]: any; // 其他字段根据数据集定义
}3. Create a record (create)
URL: POST /openapi/data/create
Request body:
{
appCode: string;
datasetCode: string;
paramMap: {
[key: string]: any; // 要创建的数据字段
}
}Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
appCode | string | ✅ Yes | App code |
datasetCode | string | ✅ Yes | Dataset code |
paramMap.[field_name] | any | Depends on the field definition | Data 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):
{
id: string | number; // 新创建记录的 ID
[key: string]: any; // 完整的记录数据(包括自动生成的字段)
}4. Update a record (update)
URL: POST /openapi/data/update
Request body:
{
appCode: string;
datasetCode: string;
paramMap: {
id: string | number; // 要更新的记录 ID
[key: string]: any; // 要更新的字段
}
}Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
appCode | string | ✅ Yes | App code |
datasetCode | string | ✅ Yes | Dataset code |
paramMap.id | string/number | ✅ Yes | ID of the record to update |
paramMap.[field_name] | any | ❌ No | Fields 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_modifiedis updated automaticallyidandgmt_createcannot be modified
Response (data field):
{
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):
{
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):
{
paging: {
// 【固定字段】分页信息
pageSize: number; // 每页记录数
totalCount: number; // 总记录数
currentPage: number; // 当前页码
}
tableData: Array<T>; // 【固定字段】数据列表数组
tableColumns: Array<{
// 【固定字段】表格列定义
title: string; // 列标题
dataIndex: string; // 字段名
}>;
}Using the SDK
Creating a client
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:
| Method | Description | Endpoint |
|---|---|---|
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
// 执行 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
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}元`);
});
}Safe execution style (recommended)
Use the sqlSafe wrapper to simplify error handling:
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
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:
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:
const response = await client.models.users.filter({
currentPage: 1,
pageSize: 20,
// 查询条件(根据数据集实际字段)
status: "active",
customer_type: "vip",
});Walk all pages:
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:
X-Time-Stamp: {timestamp}
X-App-Code: {appCode}
X-Dataset-Code: {datasetCode}
X-Token: {token}Request body:
{
"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:
{
"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:
{
"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:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
pageSize | number | No | 10 | Records per page, up to 100 |
currentPage | number | No | 1 | Current page number, starting at 1 |
Sorting:
| Parameter | Type | Required | Description |
|---|---|---|---|
ytSortList | Array | No | Sort 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:
{
// 精确匹配
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
// 查询单条数据
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:
X-Time-Stamp: {timestamp}
X-App-Code: {appCode}
X-Dataset-Code: {datasetCode}
X-Token: {token}Request body:
{
"appCode": "app-c2dd52a2",
"datasetCode": "0fefba76fe29440194841f4825df53ff",
"paramMap": {
"id": 14
}
}Response example:
{
"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
// 新增客户信息
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:
X-Time-Stamp: {timestamp}
X-App-Code: {appCode}
X-Dataset-Code: {datasetCode}
X-Token: {token}Request body:
{
"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:
{
"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
// 更新客户信息
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:
// 批量更新多个客户的状态
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:
X-Time-Stamp: {timestamp}
X-App-Code: {appCode}
X-Dataset-Code: {datasetCode}
X-Token: {token}Request body:
{
"appCode": "app-c2dd52a2",
"datasetCode": "0fefba76fe29440194841f4825df53ff",
"paramMap": {
"id": 158,
"customer_type": "vip",
"credit_level": "A",
"credit_amount": 200000.0,
"remark": "升级为 VIP 客户,提升信用额度"
}
}Response example:
{
"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():
// 推荐:使用软删除(更新状态为 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.
| Code | Description | Solution |
|---|---|---|
| 1001 | Invalid parameters | Check that the request parameters are complete and correctly formatted |
| 1002 | Signature verification failed | Check the Access Key, the signing algorithm, and the timestamp |
| 1003 | Timestamp expired | The token has expired (10 minutes); generate a new one |
| 1004 | App not found | Check the App Code |
| 1005 | Dataset not found | Check the Dataset Code |
| 1006 | Access denied | Confirm the app has access to the dataset |
| 2001 | Query timeout | Optimize the query conditions to reduce the data volume |
| 2002 | Record not found | Check the query conditions or the ID |
| 3001 | Internal server error | Contact technical support |
| 4001 | Rate limit exceeded | Slow down and add throttling |
Error response example
{
"success": false,
"msg": null,
"data": null,
"errorCode": 1002,
"errorMsg": "签名验证失败,请检查 Token 生成算法"
}SDK error handling
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
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
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
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
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
// ✅ 推荐:合理的分页大小
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
// ❌ 不推荐:循环调用 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
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
// 限制并发数
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
| Limit | Value |
|---|---|
| Per minute | 600 requests |
| Per hour | 10,000 requests |
| Per day | 100,000 requests |
Payload limits
| Limit | Value |
|---|---|
| Max records returned per query | 100 |
| Max response body | 10MB |
| Max request body | 1MB |
Concurrency limits
| Limit | Value |
|---|---|
| Max concurrent connections per app | 10 |
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
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
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
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
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
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);
}
}Related docs
- Authentication guide - auth mechanics and token management
- Quick start - learn by example
- Overview - OpenAPI at a glance
Need help?
If something goes wrong:
- Check the error codes in this document
- Browse GitHub Issues
- Contact your account manager for technical support