Tham chiếu API

Hai bề mặt khoá API: một để mua hàng từ danh mục, một để xử lý đơn hàng sản phẩm. Xác thực bằng tiêu đề X-Api-Key và gọi từ máy chủ của bạn.

Overview

This API lets another system do two things: buy from the catalogue the way a customer does, and fulfil product orders the way a collaborator does. Those are two separate surfaces with two separate kinds of key, and a key for one is refused by the other.

Everything below is versioned under /api/v1 and answers JSON.

Base URL

There is no separate api. hostname and no separate port — the API is served from the same origin as the storefront:

url cơ sở
https://rollroyce.store/api/v1

It is server-to-server

The API answers CORS preflights only for the storefront's own origin. A browser on any other origin cannot call it, and no amount of client-side configuration will change that — the refusal is not a misconfiguration, it is the deployment.

Call it from your backend. If you need it in a browser, put your own server in between; that server holds the key, which is where a key belongs anyway.

The response envelope

Every success looks the same:

json
{
  "data": {},
  "message": "A human sentence, or null",
  "meta": { "pagination": { "total": 0, "perPage": 20, "currentPage": 1, "lastPage": 1 } }
}

meta appears only on a paginated response. message is already translated for the caller's Accept-Language; when you show a result to a person, prefer it over a sentence of your own — some of these are worded deliberately.

Every failure looks the same too:

json
{
  "errors": [{ "code": "E_VALIDATION", "field": "quantity", "message": "…" }]
}

Money is a string

Every money value arrives as a string, not a number, and that is not a serialisation quirk to normalise away. The columns behind them hold four decimal places, and this catalogue prices below a cent — so "0.0125" is a real price and JSON.parse into a float will eventually lose someone's money.

Keep it a string, or move it into a decimal type. Never a JavaScript number.

The amount charged is computed by the API, never by you. Send what you want to buy; read paidAmount back.

Pagination

Every list takes page and perPage and returns meta.pagination. There is no unpaginated list anywhere in this API — a listing that looks small today is one an operator can grow tomorrow.

http
GET /api/v1/client/catalog/groups?page=2&perPage=50

perPage above 100 is a 422, not clamped.

Query strings

Query strings are parsed with a ceiling of 40 parameters — anything past the fortieth is dropped, not refused — and shallow nesting. Attribute filters use one level of it:

http
GET /api/v1/client/catalog/groups?attributes[region]=EU&attributes[tier]=pro

Arrays in query strings are not supported. Repeat-key and a[]=1&a[]=2 forms will not do what you expect.

Authentication

One header. Not Authorization, and not a bearer token — those belong to the signed-in web session and an API key is refused on every route that uses them.

http
X-Api-Key: ck_NA.eyJzZWNyZXQiOiJleGFtcGxlIn0

Two surfaces, two prefixes

PrefixKey typeReachesFor
ck_customer/api/v1/client/*Browsing the catalogue, placing and reading your own orders
lk_collaborator/api/v1/collab/*Claiming and fulfilling product orders, reading payouts

A ck_ key sent to /collab/* is a 401, not a 403 — the same answer as no key at all. The prefix is a convenience for you and for secret scanners; the server does not trust it, and checks the key's real type against the surface after verifying the secret.

Calling from a server

Writes need one extra header. Any request other than GET, HEAD or OPTIONS that carries neither Origin nor Sec-Fetch-Site is refused with 403 E_FORBIDDEN before it reaches the API at all. Origin must be the site's own origin, exactly as shown — any other value is refused the same way. If you send Sec-Fetch-Site, it wins: only same-origin and none pass.

bắt buộc khi ghi dữ liệu
Origin: https://rollroyce.store

A browser sets one of those two headers on its own, so this only ever bites server-side clients — curl, requests, axios on Node, and anything else that is not a browser.

Why this exists, and why sending it is safe. The header check is CSRF protection for the session cookie: a browser attaches that cookie by itself, so a page on another site could otherwise make a state-changing request as a signed-in user. Origin is set by the browser and page script cannot forge it, so a real cross-site attack still cannot produce it. You are not that attack — you already hold an API key. Naming the site you are talking to costs nothing and keeps the protection intact for everyone else.

GET requests never need it.

What a 401 means

Six different problems answer with the same 401, the same code, and the same message:

  • no X-Api-Key header
  • a malformed value
  • a key id that does not exist
  • a wrong secret
  • a disabled key
  • a key for the other surface

That is deliberate. Telling them apart would tell an attacker probing with crafted values which half of a guess was right. When you get a 401, check your key and your surface — the API will not narrow it down for you.

Failed authentications are budgeted per IP address: 30 a minute. Past that, every request from the address that presents a credential — a wrong one or a correct one — is a 429 with retry-after until the window resets, and the credential is not looked up at all. Only failures count, so a working client never gets near it; a service retrying a wrong key in a loop takes every other client behind the same address down with it for the rest of that minute. Fix the key, do not retry a 401.

A 403 is different, and it does say something real: the key is valid, and either it lacks the ability the route needs (E_FORBIDDEN), the account is suspended (E_ACCOUNT_BANNED), or the collaborator partnership is not live (E_COLLABORATOR_NOT_APPROVED).

Handle the secret like a password

The plaintext value leaves the server once, in the response that creates the key. Only a hash is stored, so it cannot be shown again and cannot be recovered — if it is lost, revoke the key and mint another.

Keep it server-side. A key placed in a browser bundle, a mobile app or a public repository is a key that can spend the wallet behind it.

Getting a key

You mint your own keys, from your own account, while signed in to the site. Nobody mints one on your behalf — not support, not an administrator. An administrator can revoke or disable a key, change its rate limit, or change its abilities after the fact — never read its secret.

The three routes below run on the session surface, so they are reached with a signed-in browser session and cannot be called with an API key. That is the one deliberate circularity in this API, and it is what stops a leaked key from minting more keys.

The playground cannot exercise these three — it authenticates with an API key and holds no session. Use the site while signed in.

Choosing abilities

Omit abilities and you get the default grant for the key's type: everything that type can do, minus product_order:cancel. See Abilities for the full table.

Grant the least the integration needs. A key that only reads the catalogue and places orders has no business holding credential:read, and one that never cancels should not be able to.

A collaborator key needs an approved partnership

type: "collaborator" is refused with 403 unless your account is an approved collaborator (E_COLLABORATOR_NOT_APPROVED, or E_COLLABORATOR_PROFILE_REQUIRED when there is no partnership at all). Being given the role is not enough — the check reads the partnership's live status, so a withdrawn partnership stops working immediately, on the key you already hold.

Minting is limited to 5 keys per 15 minutes per account, and an account holds at most 10 keys at once — the eleventh is a 422 E_API_KEY_LIMIT_REACHED until one is revoked (an operator can raise the ceiling for you). A name that is already used by another key of the same type on your account is a 422 on name.

Mint a key

The only way a key is ever created — and the only time its secret is shown.

POST/api/v1/account/api-keys
Xác thựcToken bearer của phiên đăng nhập — khoá API không thể gọi tuyến này.
Thay đổi trạng tháicó — không an toàn khi thử lại một cách mù quáng

Nội dung yêu cầu

TrườngKiểuRàng buộcMô tả
typebắt buộcstringcustomer | collaboratorWhich surface the key opens. A collaborator key requires an approved partnership.
namestringmax 100 chars, nullableA label for you. Unique per account and type.
abilitiesstring[]values from the ability tableOmit to get the default grant for the type. An ability outside that type's list is a 422.

Yêu cầu ví dụ

curl -X POST 'https://rollroyce.store/api/v1/account/api-keys' \
  -H 'X-Api-Key: YOUR_API_KEY' \
  -H 'Origin: https://rollroyce.store'
201The key, plus value — the plaintext secret. This is the ONLY response in the entire API that carries it. Store it now; it is not recoverable.
Phản hồi 201
{
  "data": {
    "id": 4,
    "type": "customer",
    "name": "Storefront integration",
    "abilities": [
      "catalog:read",
      "order:quote",
      "order:create",
      "order:read",
      "credential:read",
      "balance:read"
    ],
    "rateLimitPerMinute": null,
    "status": "active",
    "expiresAt": null,
    "createdAt": "2026-02-01T08:00:00.000Z",
    "value": "ck_NA.eyJzZWNyZXQiOiJleGFtcGxlIn0"
  },
  "message": "API key created"
}

Lỗi từ điểm cuối này

Trạng tháiÝ nghĩaCách xử lý
E_API_KEY_LIMIT_REACHED422The account already holds as many keys as it is allowed (10 by default).Revoke a key you no longer use, or ask an operator to raise the ceiling for your account. Nothing about the request will change on its own.

List your keys

Every key on your account. The secret is never among them.

GET/api/v1/account/api-keys
Xác thựcToken bearer của phiên đăng nhập — khoá API không thể gọi tuyến này.
Phân tranggói phản hồi chứa meta.pagination

Tham số truy vấn

TrườngKiểuRàng buộcMô tả
pageintegermin 1, default 11-based page number.
perPageinteger1-100, default 20 — above 100 is a 422, not clampedRows per page.

Yêu cầu ví dụ

curl -X GET 'https://rollroyce.store/api/v1/account/api-keys' \
  -H 'X-Api-Key: YOUR_API_KEY'
200A page of keys.
Phản hồi 200
{
  "data": [
    {
      "id": 4,
      "type": "customer",
      "name": "Storefront integration",
      "abilities": [
        "catalog:read",
        "order:quote",
        "order:create",
        "order:read"
      ],
      "rateLimitPerMinute": null,
      "status": "active",
      "lastUsedAt": "2026-02-11T14:03:22.000Z",
      "expiresAt": null,
      "createdAt": "2026-02-01T08:00:00.000Z",
      "updatedAt": "2026-02-01T08:00:00.000Z"
    }
  ],
  "message": null,
  "meta": {
    "pagination": {
      "total": 42,
      "perPage": 20,
      "currentPage": 1,
      "lastPage": 3
    }
  }
}

Revoke a key

Kills the key on the next request. Orders it placed are kept.

DELETE/api/v1/account/api-keys/:id
Xác thựcToken bearer của phiên đăng nhập — khoá API không thể gọi tuyến này.
Thay đổi trạng tháicó — không an toàn khi thử lại một cách mù quáng

Tham số đường dẫn

TrườngKiểuRàng buộcMô tả
idbắt buộcintegerdigits onlyThe key id.

Yêu cầu ví dụ

curl -X DELETE 'https://rollroyce.store/api/v1/account/api-keys/:id' \
  -H 'X-Api-Key: YOUR_API_KEY' \
  -H 'Origin: https://rollroyce.store'
200Gone. `data` is null; the message names what happened.
Phản hồi 200
{
  "data": null,
  "message": "API key deleted"
}

Revoking takes effect on the next request. Nothing is cached, so there is no window in which a revoked key still works. Orders the key placed are kept — revocation removes the credential, not the history. An id that is not one of your keys is a 404 E_API_KEY_NOT_FOUND.

Abilities

A key's abilities are chosen when it is minted and cannot be changed afterwards by the person who created it — to widen a key, mint a new one. An administrator can change them, so a key's grant is not something to hard-code on your side either.

Every route names the one ability it needs. Holding the wrong set is a 403 E_FORBIDDEN — the key is fine, the grant is not.

Customer keys

QuyềnCấp quyềnMặc định được cấp
catalog:readBrowse categories, listing groups and the products inside one.GET /client/catalog/categoriesGET /client/catalog/groupsGET /client/catalog/groups/:idGET /client/catalog/groups/:id/products
order:quotePrice a basket before committing to it. Creates nothing.POST /client/orders/quote
order:createPlace an order. Debits the account's wallet.POST /client/orders
order:readRead the key owner's own orders — metadata, never credentials.GET /client/ordersGET /client/orders/:code
credential:readDecrypt and read the delivered accounts of the owner's own completed `account` orders.GET /client/orders/:code/credentialsGET /client/orders/:code/export
balance:readRead the owner's wallet balance and the discount rate currently applied to it.GET /client/me/balance

credential:read is separate from order:read on purpose. Reading an order is reading metadata; reading a credential is taking the asset. An integration that builds reports needs the first and must not hold the second.

Collaborator keys

QuyềnCấp quyềnMặc định được cấp
product_order:readThe claim queue, the collaborator's own orders, and one order's detail.GET /collab/product-orders/queueGET /collab/product-ordersGET /collab/product-orders/:id
product_order:claimTake an unclaimed product order out of the queue.POST /collab/product-orders/:id/claim
product_order:completeMark a held order delivered, which starts its payout clock.POST /collab/product-orders/:id/complete
product_order:cancelCancel an order the collaborator is holding and refund the buyer in full.POST /collab/product-orders/:id/cancelkhông — hãy yêu cầu
payout:readRead the collaborator's own commission payout history.GET /collab/payouts
complaint:readRead the complaint items filed against the collaborator's own sales.GET /collab/complaintsGET /collab/complaints/:id
complaint:respondAnswer a complaint item: refund the buyer, or send a replacement / a re-done result.Withheld by default like product_order:cancel: the refund half moves the collaborator's own escrow to the buyer, and a leaked key holding it could do that for every open complaint.POST /collab/complaints/:id/refundPOST /collab/complaints/:id/fixkhông — hãy yêu cầu
stock:writeUpload account credentials into a group's pool, in the collaborator's own name.Granted by default. A leaked key holding it can flood a group's pool with junk rows under your name, list your entire inventory, and delete your own unsold (ready) rows outright — rotate the key. The reach stops there: delete cannot touch a row that is reserved or already sold, so no buyer is affected and no money moves, and it cannot touch another collaborator's inventory — only rows the key owner filed.POST /collab/stockGET /collab/stockPUT /collab/stock/:idDELETE /collab/stock/:id

product_order:cancel is the one ability withheld by default. It destroys a real order and refunds the buyer in full, which means a leaked key holding it can do that in bulk. Ask for it only when you need it, and preferably on a key that does nothing else.

Abilities do not cross surfaces

The two lists above are not interchangeable. Minting a customer key with payout:read is a 422 — not because the ability is dangerous, but because it could never be used: /collab/* refuses a customer key with a 401 long before any ability is checked, and a grant that can never fire is a confusing thing to hand out silently.

Discounts do not live on the key

A key carries no pricing power of its own. Two different keys on the same account, ordering the same basket, are charged exactly the same.

Whatever discount the account has — a manual rate, or one earned by tier — applies automatically on every order placed with any of its keys. You do not request it and you cannot alter it.

Discount codes are a different thing, and they do not work here at all: see the note on POST /client/orders/quote.

Rate limits

The surface budget is counted per key, not per account and not per IP. Two keys on one account have independent budgets, so a runaway script on one does not take the other down with it. The tighter budgets on particular routes are the exception: those are counted per account, shared by every key you hold and by your session on the website — a second key does not buy a second claim budget.

The surface budget

Key typeDefaultApplies to
customer (ck_)120 requests / minuteevery /api/v1/client/* route
collaborator (lk_)240 requests / minuteevery /api/v1/collab/* route

Collaborators get the larger budget because they poll a queue; a script checking four times a second is ordinary there and would be strange on the customer surface.

An administrator can set a per-key limit that overrides the default, up to a hard ceiling of 1200/minute. Your key's own limit is on GET /account/api-keys as rateLimitPerMinutenull means "use the default for the type".

Routes with a tighter budget of their own

These stack on top of the surface budget rather than replacing it. Hitting either one is a 429.

RouteExtra budget
GET /client/orders/:code/credentials10 / minute per account, shared with the website's own credential views — plus 120,000 credential rows per hour, per account
POST /collab/product-orders/:id/claim20 / minute per account, shared with claims made on the website (an operator setting; this is the default)
POST /collab/product-orders/:id/cancel10 / minute per account, shared with the website (an operator setting; this is the default)
Any request with a credential that fails to authenticate30 failures / minute per IP address — see Authentication

A lost claim race still spends a claim. The budget is charged before the handler runs, so a 409 E_PRODUCT_ORDER_ALREADY_CLAIMED costs you one of your twenty. That is worth designing around: poll the queue, claim what you intend to work on, and do not claim speculatively.

Read the headers instead of guessing

Every response carries the state of a budget it just spent from — x-ratelimit-reset is the number of seconds until that budget resets:

http
x-ratelimit-limit: 120
x-ratelimit-remaining: 118
x-ratelimit-reset: 37

On a route with a tighter budget of its own, the headers describe that budget, not the surface one. A request refused with a 401 or 403 carries none.

And a refusal tells you exactly how long to wait:

http
HTTP/1.1 429 Too Many Requests
retry-after: 37

Back off on retry-after. Do not retry immediately, and do not spread the same work across several keys to get around a limit — a limit reached that way is a sign the integration should be batching, not multiplying credentials.

Windows are fixed, not sliding

A window opens on the first request that touches a budget and resets when x-ratelimit-reset reaches zero; it does not decay gradually. Sixty requests at the end of one window and sixty at the start of the next are both allowed, and both are within the rules.

Errors

json
{
  "errors": [
    { "code": "E_VALIDATION", "field": "items.0.quantity", "message": "…" }
  ]
}

field is null for anything that is not about one input. Nested fields are dotted, so items.0.quantity points at the second line of an order — bind these to your own inputs rather than showing only the first message.

Branch on code, never on status

This is the single most important thing on this page for anyone writing a client. The status code is not enough to decide what to do, because different problems share one:

  • E_FORBIDDEN (403) means fix the key — mint one with the right ability.
  • E_ACCOUNT_BANNED (403) means stop — no key on this account will work.
  • E_COLLABORATOR_NOT_APPROVED (403) means the partnership ended — the key is still valid and will start working again if it is restored.

Three identical statuses, three unrelated responses. Read errors[0].code.

Codes on every route

Trạng tháiÝ nghĩaCách xử lý
E_UNAUTHORIZED401The key was missing, malformed, unknown, disabled, expired, or belongs to the other surface.Check the header is X-Api-Key and that the key prefix matches the surface you are calling. All of those causes answer identically on purpose — the API will not tell you which one it was.
E_FORBIDDEN403The key authenticated, but does not hold the ability this route requires.Read the ability named on the endpoint. A key's abilities are fixed when it is created; mint a new key rather than expecting this to change.
E_ACCOUNT_BANNED403The account the key acts as is suspended.Nothing the key can do. Every key on that account is refused until an operator reinstates it.
E_TOO_MANY_REQUESTS429A rate limit was exceeded.Wait for retry-after (seconds). Every response also carries x-ratelimit-remaining and x-ratelimit-reset, so you never have to discover a limit by being refused.
E_NOT_FOUND404No route matched. A path parameter of the wrong shape — a non-numeric id, an order code without the ord_ prefix — lands here, before any handler runs.Check the path. This is the router answering, so it carries no endpoint-specific code.
E_VALIDATION422One or more fields failed validation.Each entry in errors[] carries the offending field — dotted for nested values, e.g. items.0.quantity. Bind them to your own inputs rather than showing the first message alone.
E_INTERNAL_ERROR500Something failed inside the API.Retry idempotent reads. The message is deliberately generic and carries no detail — the detail is in the server log, never in your response.

Codes raised by particular endpoints are listed with each endpoint.

Retrying

SituationRetry?
429Yes, after retry-after seconds
500 E_INTERNAL_ERRORYes for reads; for POST /client/orders, check the order list first
409 E_INSUFFICIENT_STOCKYes, with a smaller quantity
409 E_PRODUCT_ORDER_ALREADY_CLAIMEDYes, against a different order
409 E_GROUP_UNAVAILABLEOnly after re-reading the group — it may be gone for good
401, 403, 404, 422No. Nothing about the request will change on its own

Order creation is not idempotent. There is no idempotency key in this API. If POST /client/orders times out, do not blindly resend it — read GET /client/orders first and check whether the order exists. A duplicate here is a duplicate charge.

500s tell you nothing, deliberately

An internal error carries a fixed generic sentence and no detail — no query, no table name, no stack. The detail exists, in the server's log. If you need it, quote the time and the endpoint when you ask.

Bề mặt khách hàng

ck_…

Mười điểm cuối để duyệt danh mục và đặt hàng với tư cách tài khoản đã tạo ra khoá này.

List categories

The public category tree, paginated.

GET/api/v1/client/catalog/categories
Xác thựcX-Api-Key, khoá khách hàng (ck_)
Quyềncatalog:read
Phân tranggói phản hồi chứa meta.pagination

Tham số truy vấn

TrườngKiểuRàng buộcMô tả
pageintegermin 1, default 11-based page number.
perPageinteger1-100, default 20 — above 100 is a 422, not clampedRows per page.

Categories are the top level of the catalogue. They carry no prices and no stock of their own — they exist to group listings, and displayPriority is the order an operator wants them shown in.

Yêu cầu ví dụ

Dùng thử trực tiếp
curl -X GET 'https://rollroyce.store/api/v1/client/catalog/categories' \
  -H 'X-Api-Key: YOUR_API_KEY'
200A page of categories.
Phản hồi 200
{
  "data": [
    {
      "id": 3,
      "name": "Example category",
      "slug": "example-category",
      "description": null,
      "image": null,
      "displayPriority": 0,
      "createdAt": "2026-01-04T09:12:00.000Z"
    }
  ],
  "message": null,
  "meta": {
    "pagination": {
      "total": 42,
      "perPage": 20,
      "currentPage": 1,
      "lastPage": 3
    }
  }
}

List groups

Listing groups, filterable and sortable. This is the catalogue.

GET/api/v1/client/catalog/groups
Xác thựcX-Api-Key, khoá khách hàng (ck_)
Quyềncatalog:read
Phân tranggói phản hồi chứa meta.pagination

Tham số truy vấn

TrườngKiểuRàng buộcMô tả
pageintegermin 1, default 11-based page number.
perPageinteger1-100, default 20 — above 100 is a 422, not clampedRows per page.
qstringmax 100 charsFree-text search over the name.
categoryIdintegermin 1Narrow to one category.
typestringaccount | productaccount (credentials are delivered) or product (a collaborator fulfils it by hand).
priceMinnumber0-1000000Lower price bound. See the note below about what a price filter does to the result set.
priceMaxnumber0-1000000Upper price bound.
hasWarrantybooleanOnly groups that carry a warranty.
attributes[key]stringup to 20 keys, key ≤ 64 chars, value ≤ 255 charsMatch a listing attribute, e.g. attributes[region]=EU.
sortstringprice | warranty | newestSort field.
orderstringasc | descSort direction.

A group is one thing you can buy. type decides how it is delivered:

  • account — stock is delivered automatically. availableStock says how many units are sellable right now, and credentials become readable once the order completes.
  • product — a collaborator fulfils it by hand. The group holds individual products, and sellingPrice on the group itself is 0 because it does not apply.

availableStock is omitted, not null, on listings where it could not be computed. Treat a missing key as unknown rather than as zero.

The listing hides an account group that has no sellable stock and does not allow preorder, and a product group with no active product. Such a group still answers GET /client/catalog/groups/:id — absent from the list is not the same as gone.

Where a group asks the buyer questions at checkout, the payload carries a requireInput array — one { key, label, type, required, … } per question. Those keys are what inputs on an order answers.

A price filter narrows the result set in a way that is easy to miss. Send priceMin, priceMax or sort=price and the listing is restricted to type="account" groups. That is not a bug: a product group's sellingPrice is 0 meaning "not applicable", so including them would sort every one of them to the front and make the filter meaningless. If you want product groups, do not filter or sort by price.

Send no sort to get the operator's own ordering (displayPriority), which is what the storefront shows.

Yêu cầu ví dụ

Dùng thử trực tiếp
curl -X GET 'https://rollroyce.store/api/v1/client/catalog/groups' \
  -H 'X-Api-Key: YOUR_API_KEY'
200A page of groups.
Phản hồi 200
{
  "data": [
    {
      "id": 12,
      "categoryId": 3,
      "type": "account",
      "name": "Example listing",
      "description": "What the buyer receives.",
      "image": null,
      "sellingPrice": "12.5000",
      "attributes": {
        "region": "EU"
      },
      "allowPreorder": false,
      "hasWarranty": true,
      "warrantyMinutes": 1440,
      "minPurchaseQuantity": 1,
      "maxPurchaseQuantity": 10,
      "displayPriority": 0,
      "soldCount": 318,
      "createdAt": "2026-01-04T09:12:00.000Z",
      "availableStock": 74
    }
  ],
  "message": null,
  "meta": {
    "pagination": {
      "total": 42,
      "perPage": 20,
      "currentPage": 1,
      "lastPage": 3
    }
  }
}

Get a group

One listing group, with its live sellable stock count.

GET/api/v1/client/catalog/groups/:id
Xác thựcX-Api-Key, khoá khách hàng (ck_)
Quyềncatalog:read

Tham số đường dẫn

TrườngKiểuRàng buộcMô tả
idbắt buộcintegerdigits onlyThe group id.

The same shape as one row of the listing, read fresh. Use it immediately before placing an order: availableStock, minPurchaseQuantity and maxPurchaseQuantity are the three values a checkout has to respect, and all three can change between a listing being cached and an order being placed.

A group an operator has hidden or locked answers 404, the same as one that never existed — visibility is not something the API narrates.

Yêu cầu ví dụ

Dùng thử trực tiếp
curl -X GET 'https://rollroyce.store/api/v1/client/catalog/groups/12' \
  -H 'X-Api-Key: YOUR_API_KEY'
200The group.
Phản hồi 200
{
  "data": {
    "id": 12,
    "categoryId": 3,
    "type": "account",
    "name": "Example listing",
    "description": "What the buyer receives.",
    "image": null,
    "sellingPrice": "12.5000",
    "attributes": {
      "region": "EU"
    },
    "allowPreorder": false,
    "hasWarranty": true,
    "warrantyMinutes": 1440,
    "minPurchaseQuantity": 1,
    "maxPurchaseQuantity": 10,
    "displayPriority": 0,
    "soldCount": 318,
    "createdAt": "2026-01-04T09:12:00.000Z",
    "availableStock": 74
  },
  "message": null
}

Lỗi từ điểm cuối này

Trạng tháiÝ nghĩaCách xử lý
E_GROUP_NOT_FOUND404No listing group with that id, or it is not publicly visible.Re-read the group list. An operator can hide or lock a group at any time.

List a group's products

The individual products inside one product-type group.

GET/api/v1/client/catalog/groups/:id/products
Xác thựcX-Api-Key, khoá khách hàng (ck_)
Quyềncatalog:read
Phân tranggói phản hồi chứa meta.pagination

Tham số đường dẫn

TrườngKiểuRàng buộcMô tả
idbắt buộcintegerdigits onlyThe group id.

Tham số truy vấn

TrườngKiểuRàng buộcMô tả
pageintegermin 1, default 11-based page number.
perPageinteger1-100, default 20 — above 100 is a 422, not clampedRows per page.

Only type="product" groups have rows here. Passing an account group's id returns an empty page, not an error — the request was well-formed and the answer is "none", which is a different thing from "wrong".

Each product's sellingPrice is per unit. An order for a product group names products and quantities in items; the group's own price is not used.

Yêu cầu ví dụ

Dùng thử trực tiếp
curl -X GET 'https://rollroyce.store/api/v1/client/catalog/groups/12/products' \
  -H 'X-Api-Key: YOUR_API_KEY'
200A page of products.
Phản hồi 200
{
  "data": [
    {
      "id": 77,
      "groupId": 12,
      "name": "Top-up 500",
      "image": null,
      "description": null,
      "sellingPrice": "4.9000",
      "displayPriority": 0,
      "createdAt": "2026-01-04T09:12:00.000Z"
    }
  ],
  "message": null,
  "meta": {
    "pagination": {
      "total": 42,
      "perPage": 20,
      "currentPage": 1,
      "lastPage": 3
    }
  }
}

Lỗi từ điểm cuối này

Trạng tháiÝ nghĩaCách xử lý
E_GROUP_NOT_FOUND404No listing group with that id, or it is not publicly visible.Re-read the group list. An operator can hide or lock a group at any time.

Quote an order

Price a basket without creating anything. Nothing is reserved and nothing is charged.

POST/api/v1/client/orders/quote
Xác thựcX-Api-Key, khoá khách hàng (ck_)
Quyềnorder:quote

Nội dung yêu cầu

TrườngKiểuRàng buộcMô tả
orderTypebắt buộcstringaccount | productWhich checkout branch this is. Explicit rather than inferred from the other fields.
groupIdbắt buộcintegermin 1The group being bought from.
quantityintegermin 1For orderType=account. Units to buy. A quote prices any quantity; the order enforces the group's bounds.
preorderbooleanAccepted for symmetry with the order body; a quote ignores it.
itemsarray1-100 linesFor orderType=product. Each line is { productId, quantity }.
inputsobjectmax 50 keys, 4000 chars per valueAnswers to the questions the group asks at checkout (its requireInput list), as a flat string map. Ignored by a quote.
codestringmax 64 charsA discount code. Accepted by the schema and then REFUSED with 422 on this surface — see the warning below.

A quote creates nothing. No order, no reservation, no charge — it prices the basket you describe and tells you what the account would pay. Stock is not held, so a quote is a good faith estimate and not a promise.

The body is the same shape as POST /client/orders. Build one object, quote it, then post it. A quote checks less than an order does: the group must exist, be active and match orderType, a product basket must name at least one line, and every product must belong to the group — but quantity bounds, stock, product availability, preorder and inputs are only enforced when the order is placed. A quote that succeeds is not an order that will.

Read paidAmount. It already has the account's standing discount applied, and discountSource says where that discount came from:

discountSourceMeaning
noneNo discount applies
user_manualA rate set on the account by an operator
user_tierA rate earned by the account's spending tier

Discount codes do not work through the API. Sending code is a 422 E_DISCOUNT_CODE_NOT_ALLOWED_VIA_API, and the order is not created. The refusal is deliberate and loud: silently ignoring the code would let a buyer pay full price believing they had a discount. Account-level discounts above still apply — you get those without asking. A quote that carries a code also spends a separate budget of 10 per minute per account before it is refused, so do not probe.

Yêu cầu ví dụ

Dùng thử trực tiếp
curl -X POST 'https://rollroyce.store/api/v1/client/orders/quote' \
  -H 'X-Api-Key: YOUR_API_KEY' \
  -H 'Origin: https://rollroyce.store' \
  -H 'Content-Type: application/json' \
  -d '{"orderType":"account","groupId":12,"quantity":2}'
200What this basket would cost. No order exists.
Phản hồi 200
{
  "data": {
    "originalAmount": "25.0000",
    "discountAmount": "2.5000",
    "discountPercent": "10.00",
    "paidAmount": "22.5000",
    "discountSource": "user_tier"
  },
  "message": null
}

Lỗi từ điểm cuối này

Trạng tháiÝ nghĩaCách xử lý
E_DISCOUNT_CODE_NOT_ALLOWED_VIA_API422A code field was sent to a quote or an order on the API surface.Discount codes cannot be spent through the API at all. Account-level discounts still apply automatically — send no code. The refusal is loud rather than silent so a caller never pays full price believing a discount was applied.
E_GROUP_UNAVAILABLE409No purchasable group with that id: it does not exist, an operator has hidden or locked it, or its type does not match orderType.Re-read the group. A missing group and an unavailable one answer the same code here, so do not treat this as temporary without checking.
E_PRODUCT_NOT_FOUND404A productId in items does not exist, or belongs to a different group.Products belong to one group; check you are ordering from the group you listed.

Place an order

Creates the order and debits the wallet. This one spends real money.

POST/api/v1/client/orders
Xác thựcX-Api-Key, khoá khách hàng (ck_)
Quyềnorder:create
Thay đổi trạng tháicó — không an toàn khi thử lại một cách mù quáng

Nội dung yêu cầu

TrườngKiểuRàng buộcMô tả
orderTypebắt buộcstringaccount | productSame discriminator as the quote.
groupIdbắt buộcintegermin 1The group being bought from.
quantityintegerwithin the group's own min/maxFor orderType=account.
preorderbooleanCurrently disabled. Accepted for compatibility but ignored: a quantity the shelf cannot cover is refused with 409 E_INSUFFICIENT_STOCK.
itemsarray1-100 linesFor orderType=product. { productId, quantity } per line.
inputsobjectmax 50 keys, 4000 chars per value, 16 KiB in totalFor orderType=product. Answers to the group's requireInput questions. A missing required answer is a 422 whose field is the question's key.
codestringmax 64 charsRefused with 422 on this surface.

This is the one endpoint that spends money. It debits the account's wallet and creates a real order. There is no sandbox and no dry run — quote it first.

Two shapes, one discriminator

orderType is explicit rather than inferred from which fields you sent, so a malformed request gets one clear error instead of a guess:

  • orderType: "account" — set quantity. Optionally preorder: true on a group that allows buying ahead of stock.
  • orderType: "product" — set items, up to 100 lines of { productId, quantity }.

inputs answers a product group's own questions (its requireInput list). Keys the group did not ask for are dropped silently; a required one that is missing is a 422 whose field is the question's key. An account order carries no inputs.

A group that is missing, hidden, locked, or of the other type is 409 E_GROUP_UNAVAILABLE — the same code for all four, so a 409 here does not mean "try again later".

201 does not mean delivered

Every order comes back 201, already paid for. Read deliveryState before reading credentials: done means the credentials are written; pending means a large order or a preorder is still being written in the background — poll GET /client/orders/:code until it is done; none is a product order, which is fulfilled by a collaborator and never has credentials; failed means delivery could not complete and an operator will refund or retry.

There is no idempotency key. If this request times out, do not resend it — read GET /client/orders and check whether the order landed. Resending blindly charges the wallet twice.

Yêu cầu ví dụ

Dùng thử trực tiếp
curl -X POST 'https://rollroyce.store/api/v1/client/orders' \
  -H 'X-Api-Key: YOUR_API_KEY' \
  -H 'Origin: https://rollroyce.store' \
  -H 'Content-Type: application/json' \
  -d '{"orderType":"account","groupId":12,"quantity":1}'
201The order, already paid for. A large order is written in the background and comes back with deliveryState pending — poll GET /client/orders/:code until it is done.
Phản hồi 201
{
  "data": {
    "orderCode": "ord_9f2c1b7e-5a40-4c6d-9b21-8e0a4d3f5c11",
    "orderType": "account",
    "groupName": "Example listing",
    "items": null,
    "quantity": 2,
    "originalAmount": "25.0000",
    "discountAmount": "2.5000",
    "discountPercent": "10.00",
    "paidAmount": "22.5000",
    "discountSource": "user_tier",
    "status": "completed",
    "isRefunded": false,
    "refundAmount": "0.0000",
    "isPreorder": false,
    "deliveryState": "done",
    "deliveredCount": 2,
    "reservedCount": 0,
    "paidAt": "2026-02-11T14:03:22.000Z",
    "createdAt": "2026-02-11T14:03:20.000Z"
  },
  "message": "Purchase completed successfully"
}

Lỗi từ điểm cuối này

Trạng tháiÝ nghĩaCách xử lý
E_DISCOUNT_CODE_NOT_ALLOWED_VIA_API422A code field was sent to a quote or an order on the API surface.Discount codes cannot be spent through the API at all. Account-level discounts still apply automatically — send no code. The refusal is loud rather than silent so a caller never pays full price believing a discount was applied.
E_GROUP_UNAVAILABLE409No purchasable group with that id: it does not exist, an operator has hidden or locked it, or its type does not match orderType.Re-read the group. A missing group and an unavailable one answer the same code here, so do not treat this as temporary without checking.
E_INSUFFICIENT_STOCK409Fewer units are sellable than were asked for.Read availableStock on the group first. It is a snapshot, so a 409 here is normal under contention rather than a bug — retry with a smaller quantity.
E_INSUFFICIENT_BALANCE409The wallet does not hold what the order costs.Top the wallet up. There is no credit and no partial fulfilment.
E_PURCHASE_QUANTITY_RANGE422quantity is outside the group's own minimum or maximum.Both bounds ride on the group payload as minPurchaseQuantity and maxPurchaseQuantity. A quote does not check them — only the order does.
E_PRODUCT_NOT_FOUND404A productId in items does not exist, or belongs to a different group.Products belong to one group; check you are ordering from the group you listed.
E_PRODUCT_UNAVAILABLE409A named product is not currently sellable.Re-read the group's product list. A quote does not check this — only the order does.
E_PURCHASE_AMOUNT_INDIVISIBLE422After the discount, the amount paid cannot give every unit at least 0.0001.Only reachable with a very large quantity of a very cheap listing. Order fewer units per request.

List your orders

Order history for the account the key acts as.

GET/api/v1/client/orders
Xác thựcX-Api-Key, khoá khách hàng (ck_)
Quyềnorder:read
Phân tranggói phản hồi chứa meta.pagination

Tham số truy vấn

TrườngKiểuRàng buộcMô tả
pageintegermin 1, default 11-based page number.
perPageinteger1-100, default 20 — above 100 is a 422, not clampedRows per page.

Every order the account has placed, newest first — including orders placed through the website, not only through this API. A key reads its owner's history, not its own.

status is the order's lifecycle (pending, processing, completed, cancelled); deliveryState is whether the goods have been written yet (none, pending, done, failed). They move independently, and for anything that reads credentials the one that matters is deliveryState.

Because website orders are included, discountSource can also be code on an order that was paid with a discount code there — a value an order placed through the API can never carry.

Yêu cầu ví dụ

Dùng thử trực tiếp
curl -X GET 'https://rollroyce.store/api/v1/client/orders' \
  -H 'X-Api-Key: YOUR_API_KEY'
200A page of orders.
Phản hồi 200
{
  "data": [
    {
      "orderCode": "ord_9f2c1b7e-5a40-4c6d-9b21-8e0a4d3f5c11",
      "orderType": "account",
      "groupName": "Example listing",
      "items": null,
      "quantity": 2,
      "originalAmount": "25.0000",
      "discountAmount": "2.5000",
      "discountPercent": "10.00",
      "paidAmount": "22.5000",
      "discountSource": "user_tier",
      "status": "completed",
      "isRefunded": false,
      "refundAmount": "0.0000",
      "isPreorder": false,
      "deliveryState": "done",
      "deliveredCount": 2,
      "reservedCount": 0,
      "paidAt": "2026-02-11T14:03:22.000Z",
      "createdAt": "2026-02-11T14:03:20.000Z"
    }
  ],
  "message": null,
  "meta": {
    "pagination": {
      "total": 42,
      "perPage": 20,
      "currentPage": 1,
      "lastPage": 3
    }
  }
}

Get an order

One order by its public code. Metadata only — no credentials.

GET/api/v1/client/orders/:code
Xác thựcX-Api-Key, khoá khách hàng (ck_)
Quyềnorder:read

Tham số đường dẫn

TrườngKiểuRàng buộcMô tả
codebắt buộcstring^ord_[A-Za-z0-9-]+$The public order code.

Orders are addressed by their public ord_… code, never by a numeric id. A value that does not match that shape is refused by the router as a 404 E_NOT_FOUND before any handler runs. An order that belongs to another account is a 404 E_ORDER_NOT_FOUND — a key never learns that a foreign code exists.

The detail does not embed the delivered accounts. An order can hold tens of thousands of them, so they live behind their own paginated endpoint.

items is the product-line snapshot for a product order, and null for an account one. Cost prices are never included — you see what you were charged.

Yêu cầu ví dụ

Dùng thử trực tiếp
curl -X GET 'https://rollroyce.store/api/v1/client/orders/ord_9f2c1b7e-5a40-4c6d-9b21-8e0a4d3f5c11' \
  -H 'X-Api-Key: YOUR_API_KEY'
200The order.
Phản hồi 200
{
  "data": {
    "orderCode": "ord_9f2c1b7e-5a40-4c6d-9b21-8e0a4d3f5c11",
    "orderType": "account",
    "groupName": "Example listing",
    "items": null,
    "quantity": 2,
    "originalAmount": "25.0000",
    "discountAmount": "2.5000",
    "discountPercent": "10.00",
    "paidAmount": "22.5000",
    "discountSource": "user_tier",
    "status": "completed",
    "isRefunded": false,
    "refundAmount": "0.0000",
    "isPreorder": false,
    "deliveryState": "done",
    "deliveredCount": 2,
    "reservedCount": 0,
    "paidAt": "2026-02-11T14:03:22.000Z",
    "createdAt": "2026-02-11T14:03:20.000Z"
  },
  "message": null
}

Lỗi từ điểm cuối này

Trạng tháiÝ nghĩaCách xử lý
E_ORDER_NOT_FOUND404No order with that code on this account. On the credentials and export routes, also: the order is not an account order, or its deliveryState is not done yet.An order that belongs to another account answers this same 404 — a key never learns that a foreign code exists. On the credentials and export routes, poll deliveryState on the order first: a preorder or a large order is written after the 201 and reaches done later.

Read delivered credentials

The decrypted accounts of your own completed account-type order.

GET/api/v1/client/orders/:code/credentials
Xác thựcX-Api-Key, khoá khách hàng (ck_)
Quyềncredential:read
Phân tranggói phản hồi chứa meta.pagination
Giới hạn tần suất10 requests per minute, per account — shared with the website's own credential views
Giới hạn tần suất120,000 credential rows per hour, per account

Tham số đường dẫn

TrườngKiểuRàng buộcMô tả
codebắt buộcstring^ord_[A-Za-z0-9-]+$The public order code.

Tham số truy vấn

TrườngKiểuRàng buộcMô tả
pageintegermin 1, default 11-based page number.
perPageinteger1-100, default 20 — above 100 is a 422, not clampedRows per page.

The only endpoint in this API that decrypts anything. It returns the account credentials delivered by a completed account order, and it is the reason credential:read is a separate ability from order:read.

It is paginated because an order can carry tens of thousands of rows, and it carries two limits rather than one: 10 requests per minute, and 120,000 credential rows per hour. Both are counted per account rather than per key, and the first is shared with the website's own credential views. The second is what stops a compromised key from draining an entire order history in one pass.

warrantyExpiresAt is null on a group with no warranty. Where it is set, it is the deadline for raising a complaint about that specific account.

Fetch these once and store them where you keep secrets. Re-reading the same page repeatedly spends a budget that exists to make bulk extraction slow, and the data does not change. Reading a page also marks those rows as viewed on the website.

An order still being written — or a product order, which never has credentials — answers 404 E_ORDER_NOT_FOUND, the same as an order that does not exist. Poll the order's deliveryState first.

Yêu cầu ví dụ

Dùng thử trực tiếp
curl -X GET 'https://rollroyce.store/api/v1/client/orders/ord_9f2c1b7e-5a40-4c6d-9b21-8e0a4d3f5c11/credentials' \
  -H 'X-Api-Key: YOUR_API_KEY'
200A page of credentials. Reading a page also marks those rows as viewed on the website.
Phản hồi 200
{
  "data": [
    {
      "id": 90211,
      "username": "delivered-account",
      "password": "delivered-secret",
      "extra": null,
      "warrantyExpiresAt": "2026-02-12T14:03:22.000Z"
    }
  ],
  "message": "Account credentials revealed",
  "meta": {
    "pagination": {
      "total": 42,
      "perPage": 20,
      "currentPage": 1,
      "lastPage": 3
    }
  }
}

Lỗi từ điểm cuối này

Trạng tháiÝ nghĩaCách xử lý
E_ORDER_NOT_FOUND404No order with that code on this account. On the credentials and export routes, also: the order is not an account order, or its deliveryState is not done yet.An order that belongs to another account answers this same 404 — a key never learns that a foreign code exists. On the credentials and export routes, poll deliveryState on the order first: a preorder or a large order is written after the 201 and reaches done later.

Download delivered credentials

The whole order in one streamed response: a text file, one account per line, or NDJSON.

GET/api/v1/client/orders/:code/export
Xác thựcX-Api-Key, khoá khách hàng (ck_)
Quyềncredential:read
Giới hạn tần suất10 requests per minute, per account — shared with the website's own credential views; one download is one request
Giới hạn tần suất120,000 credential rows per hour, per account — the whole order counts against it

Tham số đường dẫn

TrườngKiểuRàng buộcMô tả
codebắt buộcstring^ord_[A-Za-z0-9-]+$The public order code.

Tham số truy vấn

TrườngKiểuRàng buộcMô tả
formatstringtxt | ndjsontxt (default) is the text file described below. ndjson is one JSON object per line — { id, username, password, extra, warrantyExpiresAt }, the same row as the paginated read — for a program that wants the whole order in one request without parsing a delimited file.
delimiterstring: | / | | | ,txt only. What separates the three fields on each line. Fields are written as they are, never quoted — pick one your data does not contain.

The same credentials as the paginated read, delivered as one file in one request: text/plain, sent as an attachment named <code>-accounts.txt, one account per line as username<delimiter>password<delimiter>extra. There are always three fields on a line — an account with no extra ends in an empty field — lines are separated by CRLF, and the file ends on its last line.

It exists because the paginated read is throttled by request: at 100 rows a page and 10 requests a minute, an order over 1,000 accounts cannot be read in one go that way. A download is one request whatever the order size, and the server streams it a batch at a time rather than building the file in memory.

delimiter picks the separator (:, /, | or ,, default :). Fields are written exactly as delivered, never quoted or escaped, so choose one your data cannot contain.

format=ndjson streams the same rows as one JSON object per line instead — { id, username, password, extra, warrantyExpiresAt }, the row the paginated read returns — for a program that wants the whole order in one request without parsing a delimited file. It is sent inline rather than as an attachment.

The whole order counts against the 120,000-rows-per-hour budget the moment the download starts, and every row in the file is marked as viewed on the website — a buyer who has the file has every credential in it. Download once and store the file where you keep secrets.

An order still being written — or a product order, which never has credentials — answers 404 E_ORDER_NOT_FOUND, the same as an order that does not exist. Poll the order's deliveryState first. Every refusal, including a 429, is decided before the first byte, so it arrives as the usual JSON error envelope and never as a truncated file.

Yêu cầu ví dụ

curl -X GET 'https://rollroyce.store/api/v1/client/orders/ord_9f2c1b7e-5a40-4c6d-9b21-8e0a4d3f5c11/export' \
  -H 'X-Api-Key: YOUR_API_KEY'
200txt: text/plain; charset=utf-8, sent as an attachment named <code>-accounts.txt. One line per account, username<delimiter>password<delimiter>extra — always three fields, an absent extra is an empty field, lines end with CRLF, no trailing line break. ndjson: application/x-ndjson; charset=utf-8, inline, one record per line, each ending with a line feed. Either way, every row streamed is marked as viewed on the website.

Lỗi từ điểm cuối này

Trạng tháiÝ nghĩaCách xử lý
E_ORDER_NOT_FOUND404No order with that code on this account. On the credentials and export routes, also: the order is not an account order, or its deliveryState is not done yet.An order that belongs to another account answers this same 404 — a key never learns that a foreign code exists. On the credentials and export routes, poll deliveryState on the order first: a preorder or a large order is written after the 201 and reaches done later.

Get wallet balance

The wallet balance and the discount rate the account is currently getting.

GET/api/v1/client/me/balance
Xác thựcX-Api-Key, khoá khách hàng (ck_)
Quyềnbalance:read

There is no top-up endpoint on this surface. Funding a wallet happens on the website, and deliberately so: it moves real money and belongs behind a session.

Check this before a batch of orders rather than discovering 409 E_INSUFFICIENT_BALANCE partway through one. discountPercent here is the same rate a quote would apply, computed without a basket.

Yêu cầu ví dụ

Dùng thử trực tiếp
curl -X GET 'https://rollroyce.store/api/v1/client/me/balance' \
  -H 'X-Api-Key: YOUR_API_KEY'
200The balance and standing discount.
Phản hồi 200
{
  "data": {
    "balance": "184.2500",
    "discountPercent": "10.00",
    "discountSource": "user_tier"
  },
  "message": null
}

Bề mặt cộng tác viên

lk_…

Bảy điểm cuối để nhận đơn hàng sản phẩm từ hàng đợi, xử lý đơn hàng, và xem số tiền đã thanh toán.

The claim queue

Unclaimed product orders waiting for a collaborator.

GET/api/v1/collab/product-orders/queue
Xác thựcX-Api-Key, khoá cộng tác viên (lk_)
Quyềnproduct_order:read
Phân tranggói phản hồi chứa meta.pagination

Tham số truy vấn

TrườngKiểuRàng buộcMô tả
pageintegermin 1, default 11-based page number.
perPageinteger1-100, default 20 — above 100 is a 422, not clampedRows per page.

Product orders waiting for someone to take them. This is the work board.

What you see here is deliberately thinner than what you see once you hold an order: no payoutAmount, and no prices on the items. An unclaimed order shows what the work is, not what it pays — so the queue cannot be mined for the platform's margins by anyone who never intends to claim anything.

Poll it. The collaborator budget is 240 requests a minute for exactly this reason, so a check every few seconds is well within it.

Yêu cầu ví dụ

Dùng thử trực tiếp
curl -X GET 'https://rollroyce.store/api/v1/collab/product-orders/queue' \
  -H 'X-Api-Key: YOUR_API_KEY'
200A page of unclaimed orders.
Phản hồi 200
{
  "data": [
    {
      "id": 5512,
      "orderId": 8821,
      "state": "unclaimed",
      "handlerUserId": null,
      "claimedAt": null,
      "completedAt": null,
      "resultNote": null,
      "payoutReleaseAt": null,
      "payoutPaid": false,
      "payoutPaidAt": null,
      "createdAt": "2026-02-11T14:03:22.000Z",
      "order": {
        "orderCode": "ord_9f2c1b7e-5a40-4c6d-9b21-8e0a4d3f5c11",
        "items": [
          {
            "product_id": 77,
            "item": "Top-up 500",
            "quantity": 1
          }
        ]
      }
    }
  ],
  "message": null,
  "meta": {
    "pagination": {
      "total": 42,
      "perPage": 20,
      "currentPage": 1,
      "lastPage": 3
    }
  }
}

Lỗi từ điểm cuối này

Trạng tháiÝ nghĩaCách xử lý
E_COLLABORATOR_NOT_APPROVED403The account behind the key is not an approved collaborator.Checked live on every request and never cached, so a partnership that is withdrawn stops working on the very next call.

Your product orders

Orders this collaborator has claimed, in any state.

GET/api/v1/collab/product-orders
Xác thựcX-Api-Key, khoá cộng tác viên (lk_)
Quyềnproduct_order:read
Phân tranggói phản hồi chứa meta.pagination

Tham số truy vấn

TrườngKiểuRàng buộcMô tả
pageintegermin 1, default 11-based page number.
perPageinteger1-100, default 20 — above 100 is a 422, not clampedRows per page.
statestringunclaimed | claimed | completed | cancelledFilter by lifecycle state.

Orders you currently hold or have completed. payoutAmount is present here, because you hold them.

state filters the list. The four values are the whole lifecycle, but only two of them can appear here:

stateMeaning
claimedYou hold it and it is not finished
completedYou reported it done; the payout clock is running, or it has been paid
unclaimedIn the queue, nobody holds it — never yours, so never listed here
cancelledGiven up and refunded. Cancelling releases the order, so it leaves this list

Money still in escrow is a completed row with payoutPaidAt null; this is where to look for it, not the payout history.

There is no ceiling on how many orders you may hold at once. The claim rate limit is about how fast you take them, not how many you have.

Yêu cầu ví dụ

Dùng thử trực tiếp
curl -X GET 'https://rollroyce.store/api/v1/collab/product-orders' \
  -H 'X-Api-Key: YOUR_API_KEY'
200A page of your orders.
Phản hồi 200
{
  "data": [
    {
      "id": 5512,
      "orderId": 8821,
      "state": "claimed",
      "handlerUserId": 41,
      "claimedAt": "2026-02-11T14:20:00.000Z",
      "completedAt": null,
      "resultNote": null,
      "payoutAmount": "3.7500",
      "payoutReleaseAt": null,
      "payoutPaid": false,
      "payoutPaidAt": null,
      "createdAt": "2026-02-11T14:03:22.000Z",
      "order": {
        "orderCode": "ord_9f2c1b7e-5a40-4c6d-9b21-8e0a4d3f5c11",
        "items": [
          {
            "product_id": 77,
            "item": "Top-up 500",
            "quantity": 1
          }
        ]
      }
    }
  ],
  "message": null,
  "meta": {
    "pagination": {
      "total": 42,
      "perPage": 20,
      "currentPage": 1,
      "lastPage": 3
    }
  }
}

Lỗi từ điểm cuối này

Trạng tháiÝ nghĩaCách xử lý
E_COLLABORATOR_NOT_APPROVED403The account behind the key is not an approved collaborator.Checked live on every request and never cached, so a partnership that is withdrawn stops working on the very next call.

Get a product order

One order in full, including the buyer's answers to the group's questions.

GET/api/v1/collab/product-orders/:id
Xác thựcX-Api-Key, khoá cộng tác viên (lk_)
Quyềnproduct_order:read

Tham số đường dẫn

TrườngKiểuRàng buộcMô tả
idbắt buộcintegerdigits onlyThe product order id.

One order in full — but only for the order you hold. Any other order, including one still in the queue, is a 403 E_PRODUCT_ORDER_INPUT_FORBIDDEN; an id that does not exist is a 404.

That means the buyer's brief is not readable before claiming. The queue shows what the work is (the items); inputschema for what was asked, values for what the buyer replied — is only here, after the claim. Claim what the item list tells you that you can do, then read the brief.

As the holder you get payoutAmount and each item's selling_price and cost_price. The response is the same shape when the buyer's own account reads it, with payoutAmount and handlerUserId null and the prices stripped — write your client to tolerate null there.

Yêu cầu ví dụ

Dùng thử trực tiếp
curl -X GET 'https://rollroyce.store/api/v1/collab/product-orders/5512' \
  -H 'X-Api-Key: YOUR_API_KEY'
200The order.
Phản hồi 200
{
  "data": {
    "id": 5512,
    "orderId": 8821,
    "state": "claimed",
    "handlerUserId": 41,
    "claimedAt": "2026-02-11T14:20:00.000Z",
    "completedAt": null,
    "resultNote": null,
    "payoutAmount": "3.7500",
    "payoutReleaseAt": null,
    "payoutPaid": false,
    "payoutPaidAt": null,
    "createdAt": "2026-02-11T14:03:22.000Z",
    "order": {
      "orderCode": "ord_9f2c1b7e-5a40-4c6d-9b21-8e0a4d3f5c11",
      "items": [
        {
          "product_id": 77,
          "item": "Top-up 500",
          "quantity": 1
        }
      ]
    },
    "input": {
      "schema": [
        {
          "key": "server",
          "label": "Server"
        }
      ],
      "values": {
        "server": "eu-west"
      }
    }
  },
  "message": null
}

Lỗi từ điểm cuối này

Trạng tháiÝ nghĩaCách xử lý
E_COLLABORATOR_NOT_APPROVED403The account behind the key is not an approved collaborator.Checked live on every request and never cached, so a partnership that is withdrawn stops working on the very next call.
E_PRODUCT_ORDER_NOT_FOUND404No product order with that id is visible to this collaborator.Re-read the queue.
E_PRODUCT_ORDER_INPUT_FORBIDDEN403The order exists, but you do not hold it.Only the current holder (or the buyer) may read an order's detail. An unclaimed order cannot be read here — claim it first, or work from the queue listing.

Claim an order

Take an order out of the queue. Once claimed it is yours to finish.

POST/api/v1/collab/product-orders/:id/claim
Xác thựcX-Api-Key, khoá cộng tác viên (lk_)
Quyềnproduct_order:claim
Thay đổi trạng tháicó — không an toàn khi thử lại một cách mù quáng
Giới hạn tần suất20 claims per minute, per account — shared by every key you hold and by the website; a LOST race still spends one

Tham số đường dẫn

TrườngKiểuRàng buộcMô tả
idbắt buộcintegerdigits onlyThe product order id.

Takes an order out of the queue and assigns it to you. Once claimed it is yours: there is no way to hand it back. The only exits are complete and cancel, and cancelling refunds the buyer.

The claim is a single atomic write, so exactly one collaborator wins a contested order and everyone else gets 409 E_PRODUCT_ORDER_ALREADY_CLAIMED. That is normal traffic, not an error to alert on.

A lost race still spends one of your 20 claims per minute. The budget is charged before the handler runs. Claim orders you have read and intend to work on; a bot that claims the whole queue on sight burns its budget losing races.

Yêu cầu ví dụ

Dùng thử trực tiếp
curl -X POST 'https://rollroyce.store/api/v1/collab/product-orders/5512/claim' \
  -H 'X-Api-Key: YOUR_API_KEY' \
  -H 'Origin: https://rollroyce.store'
200You hold it now.
Phản hồi 200
{
  "data": {
    "id": 5512,
    "orderId": 8821,
    "state": "claimed",
    "handlerUserId": 41,
    "claimedAt": "2026-02-11T14:20:00.000Z",
    "completedAt": null,
    "resultNote": null,
    "payoutAmount": "3.7500",
    "payoutReleaseAt": null,
    "payoutPaid": false,
    "payoutPaidAt": null,
    "createdAt": "2026-02-11T14:03:22.000Z",
    "order": {
      "orderCode": "ord_9f2c1b7e-5a40-4c6d-9b21-8e0a4d3f5c11",
      "items": [
        {
          "product_id": 77,
          "item": "Top-up 500",
          "quantity": 1
        }
      ]
    }
  },
  "message": null
}

Lỗi từ điểm cuối này

Trạng tháiÝ nghĩaCách xử lý
E_COLLABORATOR_NOT_APPROVED403The account behind the key is not an approved collaborator.Checked live on every request and never cached, so a partnership that is withdrawn stops working on the very next call.
E_PRODUCT_ORDER_NOT_FOUND404No product order with that id is visible to this collaborator.Re-read the queue.
E_PRODUCT_ORDER_ALREADY_CLAIMED409The order is no longer unclaimed — another collaborator took it first, or it is already completed or cancelled.Expected, not exceptional — the queue is contended and the claim is one atomic write. Take the next one. Note that a lost race still spends a claim from your budget.

Complete an order

Report the work done. Starts the payout clock.

POST/api/v1/collab/product-orders/:id/complete
Xác thựcX-Api-Key, khoá cộng tác viên (lk_)
Quyềnproduct_order:complete
Thay đổi trạng tháicó — không an toàn khi thử lại một cách mù quáng

Tham số đường dẫn

TrườngKiểuRàng buộcMô tả
idbắt buộcintegerdigits onlyThe product order id.

Nội dung yêu cầu

TrườngKiểuRàng buộcMô tả
resultNotestringmax 5000 chars, nullableWhat you delivered. Reaches the buyer.

Reports the work done. This starts the payout clock: payoutReleaseAt comes back set — the completion time plus the operator's hold, 24 hours by default — and the money leaves escrow at that time rather than immediately.

resultNote reaches the buyer. It is how you tell them what you delivered and where to find it, so it is worth writing properly — for many orders it is the entire delivery.

Only the holder may complete. Completing twice is 409 E_PRODUCT_ORDER_STATE_INVALID, not a second payout.

Yêu cầu ví dụ

Dùng thử trực tiếp
curl -X POST 'https://rollroyce.store/api/v1/collab/product-orders/5512/complete' \
  -H 'X-Api-Key: YOUR_API_KEY' \
  -H 'Origin: https://rollroyce.store' \
  -H 'Content-Type: application/json' \
  -d '{"resultNote":"Delivered, code sent in-app."}'
200Completed. payoutReleaseAt is when the money leaves escrow.
Phản hồi 200
{
  "data": {
    "id": 5512,
    "orderId": 8821,
    "state": "completed",
    "handlerUserId": 41,
    "claimedAt": "2026-02-11T14:20:00.000Z",
    "completedAt": "2026-02-11T15:02:00.000Z",
    "resultNote": "Delivered, code sent in-app.",
    "payoutAmount": "3.7500",
    "payoutReleaseAt": "2026-02-12T15:02:00.000Z",
    "payoutPaid": false,
    "payoutPaidAt": null,
    "createdAt": "2026-02-11T14:03:22.000Z",
    "order": {
      "orderCode": "ord_9f2c1b7e-5a40-4c6d-9b21-8e0a4d3f5c11",
      "items": [
        {
          "product_id": 77,
          "item": "Top-up 500",
          "quantity": 1
        }
      ]
    }
  },
  "message": null
}

Lỗi từ điểm cuối này

Trạng tháiÝ nghĩaCách xử lý
E_COLLABORATOR_NOT_APPROVED403The account behind the key is not an approved collaborator.Checked live on every request and never cached, so a partnership that is withdrawn stops working on the very next call.
E_PRODUCT_ORDER_NOT_FOUND404No product order with that id is visible to this collaborator.Re-read the queue.
E_PRODUCT_ORDER_NOT_HANDLER403The order is held by a different collaborator.Only the holder may complete or cancel.
E_PRODUCT_ORDER_STATE_INVALID409The order is not in a state this action accepts.A completed order cannot be completed twice and a cancelled one cannot be revived. Read state before acting.

Cancel an order

Give up an order you are holding. The buyer is refunded in full.

POST/api/v1/collab/product-orders/:id/cancel
Xác thựcX-Api-Key, khoá cộng tác viên (lk_)
Quyềnproduct_order:cancel
Thay đổi trạng tháicó — không an toàn khi thử lại một cách mù quáng
Giới hạn tần suất10 cancellations per minute, per account — shared by every key you hold and by the website; tighter than every other route on this surface

Tham số đường dẫn

TrườngKiểuRàng buộcMô tả
idbắt buộcintegerdigits onlyThe product order id.

Nội dung yêu cầu

TrườngKiểuRàng buộcMô tả
reasonstringmax 500 chars, nullableWhy. Recorded on the order.

Gives up an order you are holding. The buyer is refunded in full, and the payout for it will never be released. The order is released too — it leaves your list, and this response is the last time you can read it.

This is the most dangerous call in the API. It destroys a real order and moves real money, and it is why product_order:cancel is the one ability left out of the default grant — a key must ask for it explicitly. Mint that key narrowly, and keep it out of anything that runs unattended.

It carries a tighter budget than every other route on this surface: 10 a minute. That is not a throughput limit, it is a blast radius.

reason is recorded on the order. Fill it in — it is what an operator reads when a buyer asks what happened.

Yêu cầu ví dụ

Dùng thử trực tiếp
curl -X POST 'https://rollroyce.store/api/v1/collab/product-orders/5512/cancel' \
  -H 'X-Api-Key: YOUR_API_KEY' \
  -H 'Origin: https://rollroyce.store' \
  -H 'Content-Type: application/json' \
  -d '{"reason":"Cannot source this variant."}'
200Cancelled and refunded.
Phản hồi 200
{
  "data": {
    "id": 5512,
    "orderId": 8821,
    "state": "cancelled",
    "handlerUserId": null,
    "claimedAt": null,
    "completedAt": null,
    "resultNote": null,
    "payoutAmount": "3.7500",
    "payoutReleaseAt": null,
    "payoutPaid": false,
    "payoutPaidAt": null,
    "createdAt": "2026-02-11T14:03:22.000Z",
    "order": {
      "orderCode": "ord_9f2c1b7e-5a40-4c6d-9b21-8e0a4d3f5c11",
      "items": [
        {
          "product_id": 77,
          "item": "Top-up 500",
          "quantity": 1
        }
      ]
    }
  },
  "message": null
}

Lỗi từ điểm cuối này

Trạng tháiÝ nghĩaCách xử lý
E_COLLABORATOR_NOT_APPROVED403The account behind the key is not an approved collaborator.Checked live on every request and never cached, so a partnership that is withdrawn stops working on the very next call.
E_PRODUCT_ORDER_NOT_FOUND404No product order with that id is visible to this collaborator.Re-read the queue.
E_PRODUCT_ORDER_NOT_HANDLER403The order is held by a different collaborator.Only the holder may complete or cancel.
E_PRODUCT_ORDER_STATE_INVALID409The order is not in a state this action accepts.A completed order cannot be completed twice and a cancelled one cannot be revived. Read state before acting.

Payout history

Payouts already released to you. Money still in escrow is on your product orders, not here.

GET/api/v1/collab/payouts
Xác thựcX-Api-Key, khoá cộng tác viên (lk_)
Quyềnpayout:read
Phân tranggói phản hồi chứa meta.pagination

Tham số truy vấn

TrườngKiểuRàng buộcMô tả
pageintegermin 1, default 11-based page number.
perPageinteger1-100, default 20 — above 100 is a 422, not clampedRows per page.

What has already been paid to you, most recently paid first. Every row here has payoutPaidAt set; money still in escrow is not listed — it is a completed row on your product orders, where:

  • payoutReleaseAt in the future, payoutPaidAt null — done and still in escrow.
  • payoutReleaseAt in the past, payoutPaidAt null — due, waiting on the payout run.

Escrow is a hold, not a review: nobody has to approve anything for the clock to run out. It exists so a complaint raised shortly after delivery can still be settled against money that has not moved yet.

Yêu cầu ví dụ

Dùng thử trực tiếp
curl -X GET 'https://rollroyce.store/api/v1/collab/payouts' \
  -H 'X-Api-Key: YOUR_API_KEY'
200A page of paid-out orders, most recently paid first. Only rows with payoutPaidAt set appear.
Phản hồi 200
{
  "data": [
    {
      "id": 5512,
      "payoutAmount": "3.7500",
      "completedAt": "2026-02-11T15:02:00.000Z",
      "payoutReleaseAt": "2026-02-12T15:02:00.000Z",
      "payoutPaidAt": "2026-02-12T16:00:00.000Z"
    }
  ],
  "message": null,
  "meta": {
    "pagination": {
      "total": 42,
      "perPage": 20,
      "currentPage": 1,
      "lastPage": 3
    }
  }
}

Lỗi từ điểm cuối này

Trạng tháiÝ nghĩaCách xử lý
E_COLLABORATOR_NOT_APPROVED403The account behind the key is not an approved collaborator.Checked live on every request and never cached, so a partnership that is withdrawn stops working on the very next call.

Complaints against you

Complaint items filed against your sales — the ones waiting on you and the ones already answered.

GET/api/v1/collab/complaints
Xác thựcX-Api-Key, khoá cộng tác viên (lk_)
Quyềncomplaint:read
Phân tranggói phản hồi chứa meta.pagination

Tham số truy vấn

TrườngKiểuRàng buộcMô tả
pageintegermin 1, default 11-based page number.
perPageinteger1-100, default 20 — above 100 is a 422, not clampedRows per page.
statusstringopen | fixed | escalated | refunded | rejected | doneOnly items in this status.

Every complaint item filed against one of your sales — an account you supplied or a product order you completed. One row per disputed item, not per complaint: a buyer who names ten accounts gives you ten rows, each answered on its own.

The two statuses that need you:

  • open — the buyer just filed it. Answer before collabRespondBy, or the buyer is refunded automatically and, if the money had reached you, clawed back.
  • fixed — you sent a fix and the buyer has until buyerConfirmBy to say it worked. Their silence closes the item in your favour.

escalated means the buyer said your fix did not work and an administrator decides. The three closed statuses (refunded, rejected, done) carry closedReason — which road led there.

amountAtStake is what a refund would cost you: the account's price, or the product order's remaining paid amount. alreadyPaidOut tells you whether that money is still in escrow (a refund just cancels the payout) or already in your pot (a refund is clawed back — pot, then collateral, then debt).

Yêu cầu ví dụ

Dùng thử trực tiếp
curl -X GET 'https://rollroyce.store/api/v1/collab/complaints' \
  -H 'X-Api-Key: YOUR_API_KEY'
200A page of your items, newest first. reason is the one that applies to THIS item; amountAtStake is what a refund would cost you.
Phản hồi 200
{
  "data": [
    {
      "id": 301,
      "complaintCode": "cmp_4c2a9f10-7b3e-4d88-9e61-2f5b8c7d1a90",
      "orderCode": "ord_9f2c1b7e-5a40-4c6d-9b21-8e0a4d3f5c11",
      "subjectType": "order_item",
      "orderItemId": 12044,
      "reason": "Password no longer works.",
      "attachments": [
        "/uploads/complaint/x8Fq2mZk0aLp.webp"
      ],
      "status": "open",
      "closedReason": null,
      "amountAtStake": "12.5000",
      "alreadyPaidOut": false,
      "collabRespondBy": "2026-02-13T14:20:00.000Z",
      "collabRespondedAt": null,
      "collabAction": null,
      "collabNote": null,
      "buyerConfirmBy": null,
      "buyerConfirmedAt": null,
      "buyerVerdict": null,
      "buyerNote": null,
      "refundAmount": "0.0000",
      "clawbackAmount": "0.0000",
      "resolution": null,
      "resolvedAt": null,
      "createdAt": "2026-02-11T14:20:00.000Z"
    }
  ],
  "message": null,
  "meta": {
    "pagination": {
      "total": 42,
      "perPage": 20,
      "currentPage": 1,
      "lastPage": 3
    }
  }
}

One complaint item

One item of yours. Somebody else's item is a 404, never a 403.

GET/api/v1/collab/complaints/:id
Xác thựcX-Api-Key, khoá cộng tác viên (lk_)
Quyềncomplaint:read

Tham số đường dẫn

TrườngKiểuRàng buộcMô tả
idbắt buộcintegerdigits onlyThe complaint item id.

One item, by id. An item that is not yours is a 404, not a 403 — whether a complaint exists against somebody else's sale is not yours to learn.

Yêu cầu ví dụ

Dùng thử trực tiếp
curl -X GET 'https://rollroyce.store/api/v1/collab/complaints/301' \
  -H 'X-Api-Key: YOUR_API_KEY'
200The item.
Phản hồi 200
{
  "data": {
    "id": 301,
    "complaintCode": "cmp_4c2a9f10-7b3e-4d88-9e61-2f5b8c7d1a90",
    "orderCode": "ord_9f2c1b7e-5a40-4c6d-9b21-8e0a4d3f5c11",
    "subjectType": "order_item",
    "orderItemId": 12044,
    "reason": "Password no longer works.",
    "attachments": [
      "/uploads/complaint/x8Fq2mZk0aLp.webp"
    ],
    "status": "open",
    "closedReason": null,
    "amountAtStake": "12.5000",
    "alreadyPaidOut": false,
    "collabRespondBy": "2026-02-13T14:20:00.000Z",
    "collabRespondedAt": null,
    "collabAction": null,
    "collabNote": null,
    "buyerConfirmBy": null,
    "buyerConfirmedAt": null,
    "buyerVerdict": null,
    "buyerNote": null,
    "refundAmount": "0.0000",
    "clawbackAmount": "0.0000",
    "resolution": null,
    "resolvedAt": null,
    "createdAt": "2026-02-11T14:20:00.000Z"
  },
  "message": null
}

Refund the buyer

Close the item in the buyer's favour. If the money had already been paid out to you, it is clawed back.

POST/api/v1/collab/complaints/:id/refund
Xác thựcX-Api-Key, khoá cộng tác viên (lk_)
Quyềncomplaint:respond
Thay đổi trạng tháicó — không an toàn khi thử lại một cách mù quáng

Tham số đường dẫn

TrườngKiểuRàng buộcMô tả
idbắt buộcintegerdigits onlyThe complaint item id.

Nội dung yêu cầu

TrườngKiểuRàng buộcMô tả
notestringmax 2000 chars, nullableA word to the buyer. Recorded on the item.

Close the item in the buyer's favour. This is final: the buyer's wallet is credited the full amountAtStake in the same transaction, and the item can never be reopened from this surface.

Where the money comes from depends on alreadyPaidOut:

  • false — the payout was still in escrow. It is simply never released.
  • true — you had been paid. The whole refund is recovered down the ladder: your commission pot first, your collateral deposit second, and the remainder as a negative pot balance (a recorded debt). Your spendable wallet is never touched.

Only an open item can be refunded from here; anything else is a 422 E_COMPLAINT_NOT_ACTIONABLE.

Yêu cầu ví dụ

Dùng thử trực tiếp
curl -X POST 'https://rollroyce.store/api/v1/collab/complaints/301/refund' \
  -H 'X-Api-Key: YOUR_API_KEY' \
  -H 'Origin: https://rollroyce.store' \
  -H 'Content-Type: application/json' \
  -d '{"note":"Sorry — refunded."}'
200Refunded and closed. refundAmount is what the buyer got back; clawbackAmount is what was taken from you (0 when the payout had not reached you yet).
Phản hồi 200
{
  "data": {
    "id": 301,
    "complaintCode": "cmp_4c2a9f10-7b3e-4d88-9e61-2f5b8c7d1a90",
    "orderCode": "ord_9f2c1b7e-5a40-4c6d-9b21-8e0a4d3f5c11",
    "subjectType": "order_item",
    "orderItemId": 12044,
    "reason": "Password no longer works.",
    "attachments": [
      "/uploads/complaint/x8Fq2mZk0aLp.webp"
    ],
    "status": "refunded",
    "closedReason": "collab_refund",
    "amountAtStake": "12.5000",
    "alreadyPaidOut": false,
    "collabRespondBy": "2026-02-13T14:20:00.000Z",
    "collabRespondedAt": "2026-02-11T16:00:00.000Z",
    "collabAction": "refund",
    "collabNote": "Sorry — refunded.",
    "buyerConfirmBy": null,
    "buyerConfirmedAt": null,
    "buyerVerdict": null,
    "buyerNote": null,
    "refundAmount": "12.5000",
    "clawbackAmount": "0.0000",
    "resolution": null,
    "resolvedAt": "2026-02-11T16:00:00.000Z",
    "createdAt": "2026-02-11T14:20:00.000Z"
  },
  "message": "The buyer has been refunded"
}

Send a fix

Replace the disputed account's credentials in place, or re-send a product order's result. The buyer then has a window to confirm.

POST/api/v1/collab/complaints/:id/fix
Xác thựcX-Api-Key, khoá cộng tác viên (lk_)
Quyềncomplaint:respond
Thay đổi trạng tháicó — không an toàn khi thử lại một cách mù quáng

Tham số đường dẫn

TrườngKiểuRàng buộcMô tả
idbắt buộcintegerdigits onlyThe complaint item id.

Nội dung yêu cầu

TrườngKiểuRàng buộcMô tả
notestringmax 2000 chars, nullableWhat you did. Shown to the buyer.
credentialsobjectusername required, max 500 charsAccount orders only: { username, password?, extra? } — overwrites the disputed row.
resultNotestringmax 5000 charsProduct orders only: the re-done result.

The other answer: fix it instead of paying. What "fix" means depends on the item's subjectType:

  • order_item — send credentials. They overwrite the disputed account in place, encrypted the way the original was; the buyer's order page shows the replacement immediately. The old credential is not kept.
  • product_order — send resultNote, the re-done result. The order stays completed.

Sending the wrong half is a field-level 422. On success the item becomes fixed and the buyer's clock starts (buyerConfirmBy): they either confirm (the hold is released and your payout resumes on the next run), report it is still broken (an administrator decides), or say nothing until the clock runs out — which also releases the hold. There is one round: a buyer who reports "still broken" goes straight to the administrator.

Yêu cầu ví dụ

Dùng thử trực tiếp
curl -X POST 'https://rollroyce.store/api/v1/collab/complaints/301/fix' \
  -H 'X-Api-Key: YOUR_API_KEY' \
  -H 'Origin: https://rollroyce.store' \
  -H 'Content-Type: application/json' \
  -d '{"note":"Replaced with a fresh account.","credentials":"{ \"username\": \"new.user\", \"password\": \"new-pass\" }","resultNote":"Re-delivered, see attached."}'
200The item is now fixed and waiting on the buyer; buyerConfirmBy is their deadline.
Phản hồi 200
{
  "data": {
    "id": 301,
    "complaintCode": "cmp_4c2a9f10-7b3e-4d88-9e61-2f5b8c7d1a90",
    "orderCode": "ord_9f2c1b7e-5a40-4c6d-9b21-8e0a4d3f5c11",
    "subjectType": "order_item",
    "orderItemId": 12044,
    "reason": "Password no longer works.",
    "attachments": [
      "/uploads/complaint/x8Fq2mZk0aLp.webp"
    ],
    "status": "fixed",
    "closedReason": null,
    "amountAtStake": "12.5000",
    "alreadyPaidOut": false,
    "collabRespondBy": "2026-02-13T14:20:00.000Z",
    "collabRespondedAt": "2026-02-11T16:00:00.000Z",
    "collabAction": "fix",
    "collabNote": "Replaced with a fresh account.",
    "buyerConfirmBy": "2026-02-14T16:00:00.000Z",
    "buyerConfirmedAt": null,
    "buyerVerdict": null,
    "buyerNote": null,
    "refundAmount": "0.0000",
    "clawbackAmount": "0.0000",
    "resolution": null,
    "resolvedAt": null,
    "createdAt": "2026-02-11T14:20:00.000Z"
  },
  "message": "Your fix was sent; the buyer has been asked to confirm"
}