Quick start
This guide gets your first OpenAPI call working in 5 minutes. We recommend the official SDK — it takes care of authentication, signing, and the other fiddly parts.
TIP
Use the SDK The official SDK wraps the entire low-level implementation and works out of the box. If you want to see what's underneath, read the "Under the hood" section at the end of this guide.
Prerequisites
Before you start, make sure:
✅ You have OpenAPI access (contact your account manager)
✅ You have the following credentials:
- App Code - your app code
- Access Key - your access key
- Dataset Code - your dataset code
✅ Node.js 16+ or Bun is installed
Install the SDK
npm install @lovrabet/sdk
# 或
bun add @lovrabet/sdkScenario 1: Server-side (Node.js/SSR)
For Node.js servers, Next.js SSR, API routes, and similar environments.
Basic usage
import { createClient } from "@lovrabet/sdk";
// 创建客户端(使用 accessKey)
const client = createClient({
appCode: "your-app-code",
accessKey: process.env.LOVRABET_ACCESS_KEY, // 从环境变量读取
models: {
users: {
tableName: "users",
datasetCode: "your-dataset-code",
},
},
});
// 查询数据列表
const response = await client.models.users.filter({
currentPage: 1,
pageSize: 20,
});
console.log("总条数:", response.paging.totalCount);
console.log("数据:", response.tableData);
// 查询单条数据
const user = await client.models.users.getOne("user-id");
console.log("用户详情:", user);Next.js App Router example
Inside a Next.js server component:
// app/users/page.tsx (Server Component)
import { createClient } from "@lovrabet/sdk";
const client = createClient({
appCode: process.env.LOVRABET_APP_CODE!,
accessKey: process.env.LOVRABET_ACCESS_KEY!,
models: {
users: {
tableName: "users",
datasetCode: process.env.LOVRABET_DATASET_CODE!,
},
},
});
export default async function UsersPage() {
// 直接在服务端组件中调用
const { tableData: users } = await client.models.users.filter({
pageSize: 10,
currentPage: 1,
});
return (
<div>
<h1>用户列表</h1>
{users.map((user) => (
<div key={user.id}>{user.name}</div>
))}
</div>
);
}Next.js API route example
Expose an API route for your frontend:
// app/api/users/route.ts
import { createClient } from "@lovrabet/sdk";
import { NextResponse } from "next/server";
const client = createClient({
appCode: process.env.LOVRABET_APP_CODE!,
accessKey: process.env.LOVRABET_ACCESS_KEY!,
models: {
users: {
tableName: "users",
datasetCode: process.env.LOVRABET_DATASET_CODE!,
},
},
});
export async function GET(request: Request) {
try {
const { searchParams } = new URL(request.url);
const page = parseInt(searchParams.get("page") || "1");
const size = parseInt(searchParams.get("size") || "20");
const data = await client.models.users.filter({
currentPage: page,
pageSize: size,
});
return NextResponse.json(data);
} catch (error) {
return NextResponse.json({ error: "查询失败" }, { status: 500 });
}
}Scenario 2: Browser (pre-generated token)
For public data access from the browser, anonymous users, and similar scenarios.
Step 1: Generate the token on the server
Create an API route that returns a token:
// app/api/token/route.ts
import { generateOpenApiToken } from "@lovrabet/sdk";
import { NextResponse } from "next/server";
export async function GET() {
try {
const result = await generateOpenApiToken({
appCode: process.env.LOVRABET_APP_CODE!,
datasetCode: process.env.LOVRABET_DATASET_CODE!,
accessKey: process.env.LOVRABET_ACCESS_KEY!,
});
// 返回 token、timestamp 和 expiresAt
return NextResponse.json(result);
} catch (error) {
return NextResponse.json({ error: "Token 生成失败" }, { status: 500 });
}
}Step 2: Use the token in the browser
// app/users/client-page.tsx
"use client";
import { createClient } from "@lovrabet/sdk";
import { useEffect, useState } from "react";
export default function ClientUsersPage() {
const [users, setUsers] = useState([]);
const [client, setClient] = useState(null);
useEffect(() => {
async function init() {
// 1. 获取 token
const { token, timestamp } = await fetch("/api/token").then((r) =>
r.json()
);
// 2. 创建客户端
const newClient = createClient({
appCode: "your-app-code",
token: token,
timestamp: timestamp,
models: {
users: {
tableName: "users",
datasetCode: "your-dataset-code",
},
},
});
setClient(newClient);
// 3. 查询数据
const { tableData } = await newClient.models.users.filter({
pageSize: 10,
});
setUsers(tableData);
}
init();
}, []);
return (
<div>
<h1>用户列表</h1>
{users.map((user) => (
<div key={user.id}>{user.name}</div>
))}
</div>
);
}Auto-refresh the token
Tokens last 10 minutes, so refresh them periodically:
"use client";
import { createClient, isTokenExpiring } from "@lovrabet/sdk";
import { useEffect, useState } from "react";
export default function UsersWithRefresh() {
const [client, setClient] = useState(null);
const [timestamp, setTimestamp] = useState(null);
// 初始化客户端
useEffect(() => {
fetchTokenAndCreateClient();
}, []);
// Token 过期检查
useEffect(() => {
if (!timestamp) return;
const interval = setInterval(() => {
// 检查 Token 是否即将过期(提前 1 分钟刷新)
if (isTokenExpiring(timestamp, 60000)) {
console.log("Token 即将过期,刷新中...");
fetchTokenAndCreateClient();
}
}, 30000); // 每 30 秒检查一次
return () => clearInterval(interval);
}, [timestamp]);
async function fetchTokenAndCreateClient() {
const { token, timestamp: newTimestamp } = await fetch("/api/token").then(
(r) => r.json()
);
const newClient = createClient({
appCode: "your-app-code",
token: token,
timestamp: newTimestamp,
models: {
users: {
tableName: "users",
datasetCode: "your-dataset-code",
},
users: {
tableName: "users",
datasetCode: "your-dataset-code",
},
},
});
setClient(newClient);
setTimestamp(newTimestamp);
}
// ... 其他代码
}Scenario 3: Browser (Cookie authentication)
For signed-in users reading their own private data.
"use client";
import { createClient } from "@lovrabet/sdk";
import { useEffect, useState } from "react";
export default function PrivateDataPage() {
const [data, setData] = useState([]);
useEffect(() => {
async function fetchData() {
// 不提供任何认证信息,自动使用浏览器 Cookie
const client = createClient({
appCode: "your-app-code",
models: {
orders: {
tableName: "orders",
datasetCode: "your-dataset-code",
},
},
});
// 请求会自动携带用户的登录 Cookie
const { tableData } = await client.models.orders.filter();
setData(tableData);
}
fetchData();
}, []);
return (
<div>
<h1>我的订单</h1>
{data.map((order) => (
<div key={order.id}>{order.orderNo}</div>
))}
</div>
);
}Multiple models
Configure several data models at once:
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();Advanced queries
Pagination
const response = await client.models.users.filter({
currentPage: 2,
pageSize: 50,
});
console.log("当前页:", response.paging.currentPage);
console.log("每页条数:", response.paging.pageSize);
console.log("总条数:", response.paging.totalCount);
console.log("数据:", response.tableData);Filtering
const response = await client.models.users.filter({
currentPage: 1,
pageSize: 20,
// 其他查询条件会传递到 paramMap
status: "active",
role: "admin",
});Fetch everything (walk the pages)
async function getAllUsers() {
const allUsers = [];
let currentPage = 1;
const pageSize = 50;
let hasMore = true;
while (hasMore) {
const response = await client.models.users.filter({
currentPage,
pageSize,
});
allUsers.push(...response.tableData);
// 判断是否还有更多数据
hasMore = currentPage * pageSize < response.paging.totalCount;
currentPage++;
// 避免请求过快
await new Promise((resolve) => setTimeout(resolve, 100));
}
return allUsers;
}
const allUsers = await getAllUsers();
console.log(`共获取 ${allUsers.length} 条数据`);Error handling
import { createClient, LovrabetError } from "@lovrabet/sdk";
try {
const response = await client.models.users.filter();
console.log(response.tableData);
} catch (error) {
if (error instanceof LovrabetError) {
console.error("API 错误:", error.message);
console.error("状态码:", error.statusCode);
console.error("错误详情:", error.details);
} else {
console.error("未知错误:", error);
}
}Common errors and fixes
1. Signature verification failed
Error: errorCode: 1002, errorMsg: "Signature verification failed"
Fix:
- Check that the Access Key is correct
- Load it from an environment variable instead of hard-coding it
- Verify that appCode and datasetCode match
2. Token expired
Error: errorCode: 1003, errorMsg: "Timestamp expired"
Fix:
- Make sure the client clock is accurate
- Implement automatic token refresh in the browser
- Check token lifetime with
isTokenExpiring()
3. Access denied
Error: errorCode: 1006, errorMsg: "Access denied"
Fix:
- Confirm the Dataset Code is correct
- Check that the app has access to the dataset
- Ask your account manager to confirm the permission setup
Environment configuration
Using environment variables
Create a .env.local file:
LOVRABET_APP_CODE=your-app-code
LOVRABET_ACCESS_KEY=your-access-key
LOVRABET_DATASET_CODE=your-dataset-codeThen use it in code:
const client = createClient({
appCode: process.env.LOVRABET_APP_CODE!,
accessKey: process.env.LOVRABET_ACCESS_KEY!,
models: {
users: {
tableName: "users",
datasetCode: process.env.LOVRABET_DATASET_CODE!,
},
},
});Performance tips
1. Use sensible page sizes
// ✅ 推荐:10-50 条
const response = await client.models.users.filter({
pageSize: 20,
});
// ❌ 避免:单次查询过多数据
const response = await client.models.users.filter({
pageSize: 1000, // 不推荐
});2. Don't call getOne in a loop
// ❌ 不推荐:循环调用 getOne
for (const id of userIds) {
const user = await client.models.users.getOne(id);
}
// ✅ 推荐:使用 getList 批量查询
const users = await client.models.users.filter({
id_in: userIds, // 假设支持 IN 查询
});3. Add caching
const cache = new Map();
async function getCachedUsers() {
const cacheKey = "users-list";
if (cache.has(cacheKey)) {
const { data, timestamp } = cache.get(cacheKey);
// 缓存 1 分钟
if (Date.now() - timestamp < 60000) {
return data;
}
}
const data = await client.models.users.filter();
cache.set(cacheKey, { data, timestamp: Date.now() });
return data;
}Under the hood (advanced)
INFO
For reference only The following shows how to work without the SDK. We strongly recommend the SDK unless you have special requirements.
Signature algorithm
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 HTTP request
import axios from "axios";
async function getListRaw() {
const timestamp = Date.now();
const appCode = "your-app-code";
const datasetCode = "your-dataset-id";
const accessKey = "your-access-key";
const token = generateToken(timestamp, appCode, datasetCode, accessKey);
const response = await axios.post(
"https://runtime.lovrabet.com/openapi/data/get-list",
{
appCode: appCode,
datasetCode: datasetCode,
paramMap: {
pageSize: 10,
currentPage: 1,
},
},
{
headers: {
"Content-Type": "application/json",
"X-Time-Stamp": timestamp.toString(),
"X-App-Code": appCode,
"X-Dataset-Code": datasetCode,
"X-Token": token,
},
}
);
return response.data;
}Next steps
Congratulations — you've covered the basics of Lovrabet OpenAPI. Where to go next:
- Authentication guide - the three auth modes and token management in depth
- API reference - full endpoint docs and advanced features
Need help?
If something goes wrong:
- Check the common errors section in this document
- Browse the Issues on the GitHub repository
- Contact your account manager for technical support