关于 salla-api-core
salla-api-core 是用于编写 Salla Admin API 调用的技能,面向需要访问商家资源和应用设置的开发者。它按步骤说明 OAuth 请求、分页、错误与限流处理,并要求依据各端点的 OpenAPI 架构构建调用。
主要功能
- 01使用 Bearer 令牌发送 HTTPS 请求
- 02按页遍历集合资源
- 03处理 API 错误和 429 限流
- 04读取、合并并更新应用设置
- 05依据端点 OpenAPI 架构校验请求
适用场景
- 01实现 Salla 商家 API 客户端
- 02获取分页返回的资源集合
- 03在限流或验证失败时处理响应
- 04安全更新应用设置以避免丢失字段
MCP 相关
该技能针对使用商家 access_token 的 Salla Admin API 运行时调用,并明确区别于 Partners MCP 操作。它也提及可通过 Partners MCP 的文档和工具查找资源信息。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ sallaapp/ salla-partners-agent-kit --skill salla-api-core安装后在 Claude Code 中输入/salla-api-core确认已加载。
附带文件references/api-resources.md
详细介绍
Salla 管理 API——核心流程
其他所有 Salla 技能构建于此基础之上:进行身份验证、调用资源、分页、处理错误和速率限制,并安全地读取/写入应用设置。请按顺序完成这些步骤;通过每个关卡后再继续。这是使用商家 access_token(通过 salla-app-auth 获取)运行的代码,并非 Partners MCP 操作。
基础 URL: https://api.salla.dev/admin/v2(以下所有路径均相对于此 URL)。
文档(入门):https://docs.salla.dev/421117m0.md
仅限 HTTPS——普通 HTTP 请求会被拒绝;每次调用都必须携带
Authorization: Bearer <ACCESS_TOKEN>(OAuth 2.0)。TLS 可保护传输中的数据
(文档:https://docs.salla.dev/421121m0.md、https://docs.salla.dev/421118m0.md)。
跨资源通用规则(基础、身份验证、状态码、错误/分页格式、速率限制、版本控制、多语言)摘自官方文档,具有权威性。各资源相关的路径、字段、作用域和封装格式均为示例——实际使用前,请对照当前资源文档(参见 references/api-resources.md)或 Partners MCP
(salla_apps / salla_reference)进行确认。令牌机制——存储、刷新、刷新令牌单次使用规则以及刷新互斥锁——由 salla-app-auth 负责。
闭环——由架构驱动的调用(验证,而非猜测)
针对每个资源的调用,都要依据该端点自身记录的 OpenAPI 架构构建,不要凭记忆操作。每个端点的 docs.salla.dev/<id>.md 页面都嵌入了完整的 OpenAPI 3.x 规范,位于 ```yaml 代码块中(请求/响应架构、字段类型、枚举、必填字段)——该代码块是事实来源。按以下流程操作:
- 查找端点的文档页面:通过 salla-docs(或
references/api-resources.md)找到页面,并阅读其中嵌入的openapi:YAML 块。 - 构建符合架构的请求——包括必填字段、类型和枚举。
- 调用并验证响应是否符合架构;若不匹配,请修正请求后重试(搜索 → 构建 → 验证 → 修正)。
优先采用文档中的 OpenAPI 块,不要依赖假设;如果文档不够明确,Partners MCP(salla_reference)是替代信息来源。
步骤 0——发现
- 要调用哪个资源/端点?(订单、商品、客户、设置……)
- 是否有有效的商家
access_token?(如果没有 → salla-app-auth) - 要获取单条记录还是集合?(集合 → 需要分页,见步骤 3)
步骤 1——验证身份并识别商家
每个请求都通过 Authorization 请求头中的访问令牌进行身份验证:
Authorization: Bearer <access_token>
Accept: application/json
Content-Type: application/json # for POST/PUT/PATCH
解析稳定的商家标识一次,并将其缓存:
GET https://accounts.salla.sa/oauth2/user/info
Authorization: Bearer <access_token>
merchant.id(顶层字段——user/info 没有 data 封装)是稳定的内部标识;
缓存一次,并在后续请求中重复使用。
官方规定的五种 401 情况
令牌/授权失败会返回 401,并附带包含 code 和 message 的 error 对象。Responses 文档(https://docs.salla.dev/421123m0.md)明确列出了五种情况:
| # | 情况 | error.message(示例) | 操作 |
|---|---|---|---|
| 1 | 用户已删除 | The User is not exists. | 商家已不存在——停止调用;等待重新安装。 |
| 2 | 用户未激活 | حسابك غير مفعل(账号未激活) | 停止调用;等待重新激活/重新安装。 |
| 3 | 刷新令牌已被重用 | error: "invalid_grant"——撤销访问令牌和刷新令牌 | 商家必须重新安装应用——通过 salla-app-auth 处理。 |
| 4 | 不允许该作用域 | The access token should have access to one of those scopes: products.read_write | 为应用添加该作用域;令牌无法访问此端点。 |
| 5 | 令牌无效/已过期 | The access token is invalid(已过期、应用已卸载或值无效) | 刷新令牌(salla-app-auth);若无法刷新,则重新授权。 |
情况 3 返回原始 OAuth 格式 { "error": "invalid_grant", ... }(而不是
{ status, success, error } 封装格式),并且也会使访问令牌失效——并行重用一次性刷新令牌会导致必须完整重新安装。用于防止此情况的刷新互斥锁位于 salla-app-auth 中。
读取 error.message 以便记录日志,但应根据具体情况决定行为:对于 1–3,停止操作并等待重新安装/重新激活;对于 4,修正作用域;对于 5,刷新后重试。
检查点:"GET /oauth2/user/info 是否返回 200,并且你已缓存 merchant.id(顶层字段,而不是 data.merchant.id)?"
第 2 步 — 发起请求
API 采用 URI 版本控制——基础 URL 中的 /admin/v2 就是版本。Salla 只会在发生破坏性变更时升级版本(响应格式变更、类型变更或移除部分 API);非破坏性新增内容会直接上线(文档:https://docs.salla.dev/421126m0.md)。
常见资源端点:
| 资源 | 方法 | 端点 | 文档 |
|---|---|---|---|
| 订单 | GET | /orders | https://docs.salla.dev/5394146e0.md |
| 商品 | GET | /products | https://docs.salla.dev/5394168e0.md |
| 客户 | GET | /customers | https://docs.salla.dev/841780f0.md |
每个资源组对应的入口文档,请参阅 references/api-resources.md(用于“查找正确文档”的索引)。各资源的路径/字段/作用域请查阅相应文档,而非本文档。
**多语言(可选):**部分端点会对响应进行本地化。发送
Accept-Language: <iso_code> 表示语言偏好(发送 Content-Language 声明正文所用语言)。iso_code 的取值来自 Languages 端点;默认值为 ar(文档:https://docs.salla.dev/421122m0.md)。
带正文的 2xx 响应会封装 data(204 不带正文——见下文):
{ "status": 200, "success": true, "data": {} }
| 代码 | 含义 |
|---|---|
| 200 | OK——请求成功 |
| 201 | 已创建——资源已插入/更新 |
| 202 | 已接受——资源已删除 |
| 204 | 无内容——成功,不带正文 |
最简请求助手(TypeScript):
const BASE = "https://api.salla.dev/admin/v2";
async function sallaRequest<T>(
path: string,
accessToken: string,
options: RequestInit = {},
): Promise<T> {
const method = (options.method ?? "GET").toUpperCase();
const res = await fetch(`${BASE}${path}`, {
...options,
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json",
// Content-Type only when sending a body
...(method === "GET" || method === "DELETE"
? {}
: { "Content-Type": "application/json" }),
...options.headers,
},
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
throw Object.assign(new Error(err?.error?.message ?? res.statusText), {
status: res.status,
body: err,
});
}
if (res.status === 204) return undefined as T;
return res.json() as Promise<T>;
}
检查点:“简单的 GET 请求(例如 /orders)是否返回 { status, success, data } 封装格式?”
第 3 步 — 对集合进行分页
集合端点接受以下参数(文档:https://docs.salla.dev/421124m0.md):
| 参数 | 默认值 | 最大值 | 说明 |
|---|---|---|---|
page | 1 | — | 从 1 开始 |
per_page | 15 | 60 | 最大值为 60 |
GET /orders?page=2&per_page=40
响应结构:
{
"status": 200,
"success": true,
"data": [
/* records */
],
"pagination": {
"count": 40,
"total": 120,
"perPage": 40,
"currentPage": 2,
"totalPages": 3,
"links": {}
}
}
要遍历所有页面:在 currentPage < totalPages 时循环,并递增 page。
检查点:“你可以使用 currentPage/totalPages 遍历集合的每一页吗?”
第 4 步 — 处理错误和速率限制
错误结构(单字段和多字段错误的结构相同——fields 的映射关系为字段 → 消息;
文档:https://docs.salla.dev/421123m0.md):
{
"status": 422,
"success": false,
"error": {
"code": "error",
"message": "alert.invalid_fields",
"fields": { "first_name": ["الاسم الاول للعميل مطلوب"] }
}
}
对于任何 4xx 验证错误,fields 下的每个条目都采用 字段 → [消息…] 的形式;多字段错误只会列出更多键。读取 error.fields 以显示每个字段对应的消息。
| 状态 | Slug | 含义 |
|---|---|---|
| 400 | bad_request | 参数、字段或筛选条件无效 |
| 401 | unauthorized | 令牌无效或已过期,缺少权限范围 |
| 403 | forbidden | 访问被拒绝或暂时受限 |
| 404 | not_found | 未找到资源或路径 |
| 405 | method_not_allowed | 不支持此 HTTP 方法 |
| 406 | not_acceptable | 不接受此格式 |
| 410 | gone | 资源已不可用 |
| 422 | validation_failed | 必填字段缺失或无效 |
| 429 | too_many_requests | 超出速率限制 |
| 500 | server_error | 内部服务器错误——请稍后重试 |
| 503 | service_unavailable | 暂时过载或正在维护 |
速率限制(官方信息——文档:https://docs.salla.dev/421125m0.md)
限制按商店及其套餐执行,并采用漏桶算法:
| 套餐 | 最大请求数 | 时间窗口 | 超限后的漏出速率 |
|---|---|---|---|
| Plus | 120 | 1 分钟 | 每秒 1 个请求 |
| Pro | 360 | 1 分钟 | 每秒 1 个请求 |
| Special | 720 | 1 分钟 | 每秒 1 个请求 |
每分钟的请求额度用完后,直到时间窗口重置前,你仍可按每秒 1 个请求的速率发送请求(漏出速率)。Customers 端点有更严格的独立上限:每 10 分钟 500 个请求——请相应调整以客户为主的任务的请求频率。
超出限制会返回 429 too_many_requests;持续滥用或异常模式可能导致访问暂时受限(403)。
每个响应中都包含以下标头:
| 标头 | 含义 |
|---|---|
X-RateLimit-Limit | 此套餐每分钟允许的调用次数 |
X-RateLimit-Remaining | 当前时间窗口内剩余的调用次数 |
Retry-After | 重试前需等待的秒数(429 响应中提供) |
X-RateLimit-Reset | 时间窗口重置时的 UTC epoch 秒数 |
读取实时标头以调整退避策略;以上套餐数值是规划时可参考的文档所列最大值。
if (response.status === 429) {
// Fetch Headers: use .get(), not bracket access
const retryAfter = parseInt(response.headers.get("retry-after") ?? "60", 10);
await sleep(retryAfter * 1000);
// retry the request
}
检查点:“你的客户端会读取 error.message/error.fields,并在收到 429 时根据 Retry-After 进行退避吗?”
第 5 步 — 读取和写入应用设置(读取-修改-写入)
GET /apps/{appId}/settings # fetch current settings
POST /apps/{appId}/settings # replace the ENTIRE settings object
**POST 是完整替换,而不是合并。**请求正文中省略的任何键都会被设为 null。
务必先读取、再合并,然后写入完整对象:
// 1. Fetch current settings
const { data: current } = await api.get(`/apps/${appId}/settings`);
// 2. Merge your change
const updated = { ...current, new_key: "value" };
// 3. POST the full merged object
await api.post(`/apps/${appId}/settings`, updated);
在 Partners Portal 中输入的设置(或通过 Partners MCP salla_settings
工具定义的设置——参见 salla-app-settings)可在 App Functions 中通过 context.settings 获取 —
无需在此处调用 API。
检查项:“你的设置更新是否先读取 → 合并 → 再 POST 完整对象(没有丢弃任何键)?”
快速参考文档链接
| 主题 | URL |
|---|---|
| 入门指南 | https://docs.salla.dev/421117m0.md |
| 授权(OAuth 2.0) | https://docs.salla.dev/421118m0.md |
| 安全 | https://docs.salla.dev/421121m0.md |
| 响应和错误 | https://docs.salla.dev/421123m0.md |
| 分页 | https://docs.salla.dev/421124m0.md |
| 速率限制 | https://docs.salla.dev/421125m0.md |
| 版本控制 | https://docs.salla.dev/421126m0.md |
| 多语言 | https://docs.salla.dev/421122m0.md |
| 资源索引(查找合适的文档) | references/api-resources.md |
| 应用设置指南 | salla-app-settings 技能(references/form-builder.md) |