配额与速率限制
Kaleidr 通过两种不同方式限制使用量,两者均会返回 HTTP 429:
- 配额是组织在一个计费周期内可以使用的总量。用尽后,需要等待周期重置或增加额度。
- 速率限制规定调用接口的速度。达到限制时,需降低调用频率并等待几秒钟。
响应正文会指出触发的是哪一种限制。本页列出了各类响应格式、每次调用的计费方式以及相应的处理方法。
两类额度池
使用量以组织为单位,分别计入两个额度池。
| 额度池 | 消耗额度的操作 | Free | Pro | Enterprise |
|---|---|---|---|---|
| AI 点数 | 聊天回答、地点查询、地址搜索和设计分析 | 每周期 500,000 | 每周期 5,000,000 | 按合同定制 |
| 地图加载次数 | 每次 Tile 会话,以及每次查看已发布或嵌入的地图 | 每周期 50,000 | 每周期 50,000 | 基础额度相同;更高用量按合同约定 |
关于这两类额度池,有三点容易被误解:
- AI 点数是整个组织共用的一个额度池。 API 密钥和团队在 Kaleidr 自有应用中的操作消耗的是同一份 AI 点数。没有独立的 API 预算,所有密钥共享该额度池。
- 周期以实际计费周期为准。 付费订阅按订阅开通日开始的月度周期计算;没有订阅时按自然月计算。周期在美国东部时间午夜切换。
- 购买的额外套餐会提高上限。 AI 点数包和地图加载次数 包会叠加到套餐额度中,响应中的
limit已包含这些增量。
套餐变更会在几分钟内应用到现有密钥,无需创建新密钥。
每种调用如何计费
| 调用 | 额度池 | 费用 |
|---|---|---|
POST /chat/control/stream, /chat/summary/stream, /chat/button/stream, /spot/overview/stream | AI 点数 | 回答消耗的输入与输出 token 总数。参见流式调用的计费方式。 |
POST /chat/control/route | 无 | 不收费。规划路线时会暂时预留额度,完成后始终释放。 |
POST /spot | AI 点数 | 每次调用按固定费用计费,无论是否找到内容:当 want 包含 content 或 photos 且本次调用需要获取内容时,按 /retrieval/poi/enrich 的费用计算;否则,当它包含 address 时,按 /retrieval/geocode/forward 的费用计算。此前请求已获取的内容不收费。仅请求地点、区域或边界不收费。 |
GET /retrieval/poi/enrich | AI 点数 | 每次调用收取固定费用,即使未找到内容也会收费。 |
GET /retrieval/geocode/forward | AI 点数 | 每次调用收取较低的固定费用,即使没有结果也会收费。如果查询本身失败,则不收费。 |
POST /design/analyze | AI 点数 | 每次成功调用收取固定费用;调用失败不收费。 |
POST /design/apply-direct, GET /design/styles, /design/samples, /design/themes | 无 | 不收费。 |
POST /sdk/sessions,用于 chat、editor、viewer | 无 | 不收费。 |
POST /sdk/sessions,用于 tile | 地图加载次数 | 每次交换计入一次地图加载。 |
POST /sdk/share-sessions | 无 | 不收费。通过该会话发起的聊天调用计入地图所有者的额度。 |
POST /inference-api/vendor/mapping/propose,即业务数据上传 API 的字段映射建议 | AI 点数 | 按生成建议所使用的 token 计费。调用使用账户登录身份,而非 API 密钥。点数用尽时返回 429 quota_exceeded;此外,每个账户每小时最多可提交 30 次建议,这两种 429 响应的正文不同。上传、发布、取消发布和删除不收费。 |
当前使用量和限制可在 kaleidr.com/billing 查看。
流式调用如何计费
流式调用只有在结束后才能确定实际成本,因此计费分为两个步骤:
- 预留。 请求被接受时,系统会从额度池中预留固定数量的点数。如果剩余额度不足以完成预留,请求会在生成任何回答之前以
quota_exceeded被拒绝。因此,即使实际费用可能更低,调用开始时仍需要足够的可用额度。 - 结算。 流结束后,预留金额会替换为实际费用,即回答所使用的 token 数量。实际费用可能高于或低于预留金额。
如果主动中断流,或流在回答完成前失败,实际 token 数量无法确定,系统将按估算值收费:
| 流 | 未完成时的收费方式 |
|---|---|
/chat/control/stream | 取以下两者较大值:所提交对话的估算量(请求中的 user 和 assistant 消息),或已输出文本的估算量(后者计算至预留额度)。费用可能高于预留金额。 |
/chat/summary/stream, /chat/button/stream, /spot/overview/stream | 按已输出文本估算,且绝不超过预留金额。 |
因此,提前关闭聊天流并不能让长对话免费:即使几乎未读取回答,也至少会按请求本身的大小收费。请仅发送下一次回答所需的最近几轮对话,以减小请求体积。参见读取流式响应。
如果流运行期间达到额度限制,当前流不会中断。系统在接收请求时进行检查;下一次请求才会被拒绝。
被拒绝的请求不收费。 如果请求在处理前就被拒绝(原因包括速率限制、并发限制、验证错误或请求正文超出大小限制),不会继续占用 AI 点数或并发名额。可以重试,而无需为此次拒绝付费。
单个密钥的额度上限
密钥可以设置月度上限(monthly_cap_tokens,通过密钥管理 API 配置),用于限制该密钥在一个计费周期内可消耗的组织 AI 点数。组织额度池仍是最终总上限。
如果某次请求会使密钥超过其上限,系统也会返回 quota_exceeded,但其中的 limit 和 used 表示该密钥的额度值,而非组织额度池的值。
配额超限
HTTP/1.1 429 Too Many Requests
Retry-After: 1296000
Content-Type: application/json
{
"error": "quota_exceeded",
"detail": "Usage limit reached. Please try again later.",
"product": "b2b",
"meter": "b2b.tokens",
"tier": "pro",
"limit": 5000000,
"used": 4999100,
"remaining": 900,
"reset_at": "2026-11-01T04:00:00+00:00",
"period": "month",
"reset_in_seconds": 1296000
}
| 字段 | 含义 |
|---|---|
error | 此情况下始终为 quota_exceeded。应根据此字段进行分支处理。 |
detail | 供用户阅读的说明语句。请勿对其进行程序化解析。 |
product, meter | 标识 API 的 AI 积分计数器。对于平台密钥,始终分别为 b2b 和 b2b.tokens。 |
tier | 你的组织所使用的套餐。 |
limit, used, remaining | 共享积分池中的积分数值;如果调用因密钥专属限额被拒绝,则为该密钥的数值。remaining 可能大于零:调用被拒绝是因为剩余额度不足以完成临时预留。 |
reset_at | 周期结束的时间,采用包含时区偏移量的 ISO 8601 时间戳。请使用此值告知用户服务何时恢复。 |
period | month。 |
reset_in_seconds | 距离 reset_at 的秒数,即积分池重置周期的结束时间(订阅计费周期;若没有订阅,则为自然月)。Retry-After 响应头包含相同的数值。 |
响应正文采用扁平结构:各字段位于顶层,而不是包含在 detail 对象中。
使用会话令牌时
上述正文适用于使用服务器密钥或旧版密钥的请求。使用会话令牌的请求在访客浏览器中运行,因此拒绝响应不会泄露组织的套餐、已用额度或额度上限。无论会话由可公开密钥交换而来,还是属于已发布地图的访客会话,都遵循这一规则。
{
"error": "quota_exceeded",
"detail": "Usage limit reached. Please try again later.",
"meter": "b2b.tokens",
"reset_at": "2026-11-01T04:00:00+00:00",
"reset_in_seconds": 1296000
}
| 字段 | 服务器密钥或旧版密钥 | 会话令牌 |
|---|---|---|
error, detail | 是 | 是 |
meter | 是 | 是 |
reset_at, reset_in_seconds | 是 | 是 |
product, period | 是 | 否 |
tier | 是 | 否 |
limit, used, remaining | 是 | 否 |
HTTP 状态、Retry-After 响应头及其含义保持一致。在浏览器代码中,应根据 error 分支处理并显示 reset_at;不要尝试读取 limit、used 或 remaining。如需显示用量数字,请在后端使用服务器密钥读取,或前往 kaleidr.com/billing 查看。
如果响应正文显示 used: 0,而您知道组织此前有过活动,说明 API 无法读取使用量,因此为安全起见拒绝了调用。此时应在数秒后重试,而不是等待 reset_at。
速率限制与并发限制
每次运行时调用前都会检查三种限制。超过任一限制的请求会立即被拒绝,不会进入队列。
| 限制 | 统计单位 | 适用范围 |
|---|---|---|
| 每分钟请求数 | 密钥 | 在滚动的 60 秒窗口内,统计使用该密钥及通过该密钥交换的所有会话发起的每次运行时调用。使用同一可公开密钥的网站访客共享这一限额。 |
| 每分钟会话交换次数 | 密钥 | 针对 POST /sdk/sessions 的调用。其限额独立计算,因此挂载嵌入组件不会占用运行时请求数。 |
| 并发请求数 | 组织 | 组织所有密钥同时进行的调用。流式请求在保持打开期间一直计入并发数量。 |
无法通过 API 密钥页面或密钥管理 API 修改密钥的每分钟请求限制。如果流量需要更高的调用速率,请联系我们。
以下每种拒绝都使用相同的响应正文结构和响应头:
HTTP/1.1 429 Too Many Requests
Retry-After: 5
Content-Type: application/json
{ "error": "rate_limited", "detail": "API key rate limit exceeded. Slow down." }
error | 含义 | 处理方式 |
|---|---|---|
rate_limited | 密钥的每分钟请求数或会话交换额度已用尽。 | 等待 Retry-After 指定的秒数后重试,并加入随机抖动。 |
org_overloaded | 您的组织同时运行的调用过多。 | 等待某个进行中的调用结束后再试,关闭不再需要的流。 |
b2b_overloaded | API 对所有调用方的容量已达上限。 | 等待 Retry-After 指定的时间,再采用退避策略重试。 |
limiter_unavailable | 速率限制服务未能返回结果。 | 稍后重试。降低请求速率无济于事,因为问题并非您的调用频率。 |
visitor_limit_reached | 某位已发布地图聊天访客的每分钟或每日请求次数过多。 | 告知访客稍后再试,参见已发布地图上的聊天功能。 |
在网络边缘还会针对 IP 地址和凭据限制请求速率。被拦截的请求不会返回本页列出的 JSON 响应正文。对于没有 Kaleidr 格式正文的 4xx 响应,应降低请求频率并稍后重试。
地图加载次数
每当 Kaleidr 地图为访客启动时,都会计入一次地图加载:
- 每次针对
product: "tile"的会话交换,包括 SDK 自动刷新以及组件的每次重新挂载; - 每次查看已发布或嵌入的地图。
嵌入式 Editor 的会话交换不计入地图加载。相反,其携带可公开密钥的底图请求(样式、精灵图 和字体)会按批次统计:组织在一小时内每发起 20 次此类请求,事后增加一次地图加载。这些请求不会因为超出额度而被拒绝。地图瓦片数据本身不计入统计。
所有套餐达到额度上限后都会停止加载。 额度池用尽后,下一次加载会被拒绝且不计数。付费套餐也没有额外缓冲额度;购买地图加载包可以立即提高上限。
通过会话交换返回的拒绝响应如下:
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
{ "detail": "map_load_quota_exceeded" }
此响应正文将错误代码放在 detail 中,而不是采用上 文的扁平配额响应结构,且没有 Retry-After 响应头。在周期重置或增加额度包之前,重试无法解决问题。网页中的 Tile 嵌入组件将无法启动,SDK 会记录 kaleidr session exchange failed (429)。
直接请求地图瓦片
底图瓦片服务器并不是公开的瓦片 API。要显示 Kaleidr 底图,请使用受支持的Tile 产品。如果对 tile.kaleidr.com 的请求不是从允许来源上的 Kaleidr 嵌入组件发出,或者没有有效的可公开密钥,请求就会被拒绝:
HTTP/1.1 429 Too Many Requests
Retry-After: 3600
Cache-Control: no-store
Content-Type: application/json
{
"error": "tile_access_restricted",
"message": "Direct access to tile.kaleidr.com requires a Kaleidr publishable key. Mint a free one at kaleidr.com/api-keys and pass it as ?key= from an allowed origin. Kaleidr-hosted maps and embeds are unaffected.",
"docs": "https://docs.kaleidr.com/get-an-api-key"
}
此响应来自瓦片服务器而不是平台 API,因此使用 message 字段,而平台 API 使用 detail。新建或修改的密钥可能需要几分钟才能用于瓦片访问。
流式响应中的 quota 事件
聊天流会在完成计费后报告额度池状态。quota 是该流的最后一个事件,在 end 或 error 之后发送:
event: quota
data: {"type": "quota", "product": "b2b", "meter": "b2b.tokens", "tier": "pro", "used": 184223, "limit": 5000000, "reset_at": "2026-11-01T04:00:00+00:00", "period": "month"}
这是使用服务器密钥或旧版密钥读取流时收到的事件。如果使用会话令牌(由可公开密钥交换得到,或由已发布地图的访客会话提供)读取流,则该事件不包含套餐信息和使用量数字:
event: quota
data: {"type": "quota", "meter": "b2b.tokens", "reset_at": "2026-11-01T04:00:00+00:00"}
| 字段 | 服务器密钥或旧版密钥 | 会话令牌 |
|---|---|---|
type, meter, reset_at | 是 | 是 |
product, period | 是 | 否 |
tier | 是 | 否 |
used, limit | 是 | 否 |
使用服务器密钥时,可以凭此事件更新使用量仪表,而无需第二次请求。但有三项限制:
- 事件没有
remaining字段。请自行计算limit - used。 - 此事件尽力发送,但不保证一定到达。流可能结束而未发送该事件,因此不要一直等待它。
- 该事件仅用于报告,而非提前预警。它绝不会出现在回答中途,也不能中断流。
要收到该事件,请持续读取,直到响应正文关闭。如果客户端在 end 处停止,就会错过该事件。
在代码中处理 429
决定是否重试前,请先读取响应正文。仅凭 HTTP 状态码无法确定是否应该重试。
| 收到的响应 | 是否重试? | 何时重试 |
|---|---|---|
error: "quota_exceeded" | 否 | 等待 reset_at 所示时间,或增加点数、提高密钥上限后再试。向用户显示额度已用尽的提示。 |
error: "rate_limited", "org_overloaded", "b2b_overloaded", "limiter_unavailable" | 是 | 等待 Retry-After 指定的秒数,加上随机抖动,最多重试几次。 |
error: "visitor_limit_reached" | 否 | 稍后再试,并告知访客。 |
detail: "map_load_quota_exceeded" | 否 | 等待计费周期重置,或增加地图加载包后再试。 |
error: "tile_access_restricted" | 否 | 修正嵌入方式:在允许来源上使用带可公开密钥的 Tile 产品。 |
const RETRYABLE = new Set(['rate_limited', 'org_overloaded', 'b2b_overloaded', 'limiter_unavailable']);
async function callKaleidr(url, init, attempt = 0) {
const res = await fetch(url, init);
if (res.status !== 429) return res;
const body = await res.json().catch(() => ({}));
if (RETRYABLE.has(body.error) && attempt < 3) {
const seconds = Number(res.headers.get('retry-after')) || 5;
await new Promise((resolve) => setTimeout(resolve, (seconds + Math.random()) * 1000));
return callKaleidr(url, init, attempt + 1);
}
const error = new Error(body.error || body.detail || 'rate limited');
error.status = 429;
error.code = body.error || body.detail;
error.resetAt = body.reset_at; // present for quota_exceeded
throw error;
}
在浏览器中,可以通过 JavaScript 读取平台 API 响应 中的 Retry-After 响应头。