Build on KioskCluster
One GraphQL API behind the dashboard and every storefront: the calls our own apps make, with refusals you can act on.
On this page
01Two endpoints
Everything is GraphQL over HTTPS: a POST of JSON with a query and its variables. The dashboard and every storefront use these same endpoints.
| Endpoint | For | Sends |
|---|---|---|
/graphql | Running a business: signing staff in, the merchant, its businesses and team | authorization: Bearer kcs_…, except on the sign-in calls |
/shop/graphql | A storefront, and its shoppers' accounts | authorization: Bearer kcf_…, and kioskcluster-customer: Bearer kcc_… once a shopper signs in |
Both live at https://api.kioskcluster.com.
02Your first call
Sign-in calls carry no token. Ask for a code for an email address or phone number that has a KioskCluster account:
curl https://api.kioskcluster.com/graphql \
-H 'content-type: application/json' \
-d '{
"query": "mutation RequestLoginCode($identifier: String!) { auth { requestLoginCode(identifier: $identifier) { challengeId identifierType resendAvailableAt } } }",
"variables": { "identifier": "you@yourshop.com" }
}'The code goes to that email or phone. Send it back with the challengeId to get a session token:
mutation VerifyLoginCode($challengeId: ID!, $code: String!) {
auth {
verifyLoginCode(challengeId: $challengeId, code: $code) {
token # kcs_… a staff session, for the Authorization header
expiresAt
userId
isNewUser
}
}
}Then ask who you are, and which merchants you belong to:
curl https://api.kioskcluster.com/graphql \
-H 'content-type: application/json' \
-H 'authorization: Bearer kcs_…' \
-d '{ "query": "query Start { auth { me { id name email phone } } organizations { myOrganizations { id name slug status } } }" }'03Tokens
Every token is a four-letter prefix and 43 characters of base64url, sent as Bearer. The prefix says what it is for:
| Prefix | What it is | Lasts |
|---|---|---|
kcs_ | A staff member's session, from signing in | Up to 30 days, or until signed out |
kck_ | A business's API key, for your own servers. Owners and admins manage them, and signing people out never stops them | Until revoked |
kcf_ | A storefront's token. It is public by design: it only says which store is asking | No sign-in behind it |
kcc_ | A shopper's session on one store | Up to 30 days, or until signed out |
kcf_ token is the only one meant to be seen.04Signing in
Staff sign in on /graphql under auth, and shoppers on /shop/graphql under account, the same three ways:
- A code:
requestLoginCode(identifier)sends 4 digits to the email or phone, thenverifyLoginCode(challengeId, code)answers a token. A code works for 10 minutes and 5 tries. A new one can be sent after 60 seconds, and at most 10 an hour. - A password, once one is set:
loginWithPassword(identifier, password). Passwords are 10 to 200 characters, and lock for 15 minutes after 5 wrong tries. - Google:
signInWithGoogle(idToken, nonce), with an ID token from Google Identity Services that is at most 10 minutes old.
A signed-in person can list their sessions (mySessions: device, IP address, started, last used, expires), end one (revokeSession), or every other (signOutOtherSessions).
05Storefronts
A storefront sends its public kcf_ token on every call. When a shopper signs in, the reply's token goes in a second header, so the store and the shopper are never confused:
POST https://api.kioskcluster.com/shop/graphql
authorization: Bearer kcf_… # the store's public token
kioskcluster-customer: Bearer kcc_… # only once a shopper has signed in
query ShopMe { account { me { customerId name emails phones hasPassword } } }If the shopper's session has expired, the server refuses the call. Drop the second header and carry on as a guest.
06Refusals
Read a refusal from errors[0].extensions.code, never from the HTTP status. The message is written to be shown to a person.
{
"data": null,
"errors": [
{
"message": "orders:manage is not in your role",
"extensions": { "code": "MISSING_PERMISSION" }
}
]
}| Code | What it means |
|---|---|
VALIDATION_ERROR | An argument is wrong. The message says which, in words you can show. |
UNAUTHENTICATED | No token, or one that has expired or been signed out. Sign in again. |
MISSING_BUSINESS_SCOPE | The call names a merchant you are not a member of. |
MISSING_PERMISSION | Your role doesn't include what the call needs. The message names the permission. |
MERCHANT_SUSPENDED | The merchant is suspended, for example over an unpaid plan. |
PLAN_LIMIT | The plan's limit is reached, such as its number of products or staff accounts. |
NOT_IN_PLAN | The feature isn't part of the merchant's plan. |
NOT_FOUND | Nothing with that id, or nothing you can see. |
RATE_LIMITED | Too many attempts. Wait, then try again; the message says what to wait for. |
NOT_CONFIGURED | A way to sign in, such as codes by email or Google, isn't set up. Use another. |
INTERNAL | Our fault. Try again shortly. |
A query that isn't valid GraphQL answers GraphQL's own GRAPHQL_PARSE_FAILED, GRAPHQL_VALIDATION_FAILED or BAD_USER_INPUT.
07Roles and permissions
A call can do only what the signed-in person's role allows. A role is given for the whole merchant, for one business, or for one location. Reports, settings, the team, billing, API keys and opening a business never come with a role for a single location.
| Permission | Owner | Admin | Manager | Staff | Accountant |
|---|---|---|---|---|---|
orders:viewSee orders | Yes | Yes | Yes | Yes | Yes |
orders:manageConfirm, update and cancel orders | Yes | Yes | Yes | Yes | No |
refunds:decideApprove or refuse refunds | Yes | Yes | Yes | No | Yes |
products:viewSee products | Yes | Yes | Yes | Yes | No |
products:manageAdd and change products | Yes | Yes | Yes | No | No |
stock:viewSee stock | Yes | Yes | Yes | Yes | No |
stock:adjustChange stock counts | Yes | Yes | Yes | No | No |
customers:viewSee customers | Yes | Yes | Yes | Yes | No |
customers:manageChange customers | Yes | Yes | Yes | No | No |
reports:viewSee reports | Yes | Yes | Yes | No | Yes |
settings:manageChange the business's settings | Yes | Yes | No | No | No |
members:viewSee the team | Yes | Yes | Yes | No | No |
members:manageInvite and change the team | Yes | Yes | No | No | No |
billing:viewSee the plan and invoices | Yes | Yes | No | No | Yes |
billing:payPay for the plan | Yes | Yes | No | No | No |
apikeys:manageCreate and revoke API keys | Yes | Yes | No | No | No |
businesses:createOpen another business | Yes | No | No | No | No |
08Limits
- A storefront's calls are limited to 1,200 a minute, and repeated calls with a bad storefront token are slowed down.
- Failed sign-ins are slowed down too. Over the limit, a call answers
RATE_LIMITED; wait, then try again. - Plans set how many storefronts, products and staff accounts a merchant has. Going over answers
PLAN_LIMIT. See Compare plans.
09Payments and Hubtel
Customers pay through the merchant's own Hubtel account. Hubtel tells KioskCluster about each payment at the store's own webhook, shown in the dashboard under Settings, Payments:
https://api.kioskcluster.com/webhooks/hubtel/<your-store-link>An order counts once Hubtel confirms its payment. An unpaid order is released after 30 minutes by default; the merchant can change that.
10Getting an API key
API keys belong to a business, and only its owner or an admin can have one made. Write to support@kioskcluster.com from that account with your store's link and what you are building.