XP as a ledger of signed integer movements, what a balance is, and what an earn and a redemption actually do.
XP is a whole number of points a customer holds under your tenant. Every movement of it is recorded as one transaction, so the ledger is the list of movements and a balance is what those movements have settled to. Two operations move it: an earn credits XP to a customer, and a redemption spends it on a reward you published.
Model an XP amount as a signed integer in your own code, and never as a floating point value. Two reasons, both practical. The first is direction, and the sign is yours to apply rather than something the API hands you: amount comes back as an unsigned magnitude on an earn and on a redemption alike, and type is the field that says which way the movement went. xp_earn is a credit and xp_redeem is a debit, so summing the amounts without reading type counts a spend as a credit and overstates the history by twice what was redeemed. Two more values exist on type, xp_bonus and xp_admin_adjust. Nothing writes either today, so a switch over the four has two branches with no direction to assign yet rather than a miscount waiting to happen. The second reason is that the values are exact whole numbers with no smallest fractional unit, because the XP token declares no decimal places, so there is no such thing as part of a point. One thing to filter before you add anything up: a transaction at status failed is not a movement. The transaction list applies no status filter unless you ask for one, so a read with no filter hands you initialized, pending and failed rows next to the settled ones, and a naive sum counts a failed redemption as a spend. Every amount the API returns is a string, including the balance, and that is deliberate. A balance can grow past the range a floating point number represents exactly, and a client that parses it as one starts losing low digits silently rather than failing. Parse the amounts you read into whatever your language calls a big integer, or keep them as strings and hand them to an integer type at the edge. The request direction is not the same, so do not send a string back. POST /gateway/xp/earn declares amount as a JSON number: send it unquoted, as a whole number inside the range a JSON number represents exactly.
A balance is the XP held on chain for that customer, summed across the wallet addresses KINTRA manages for them. It is not a running total that the API increments as writes arrive: the platform reads it back from the token and reports what the token answered. Read one with GET /gateway/customers/{userId}/xp/balance, which returns it as data.balance. That is the number to show a customer and the number to check before you offer them a redemption. The practical consequence of a balance coming from the chain is timing, and it is the one thing about XP worth internalising before you write the integration:
A write does not move the balance. An earn or a redemption is accepted, recorded, and queued for the chain. The balance changes when that chain work settles and is picked up, which is after the response has already come back to you. So a read taken immediately after a write can still answer the old number. That is not an error and retrying the write is the wrong response, because a second earn grants a second time. Treat the response to the write as the receipt that it was accepted, and read the balance when you need to display it.
POST /gateway/xp/earn credits an amount of XP to a customer, with optional notes recorded against the entry. Call it when the customer does the thing you reward. The response carries the transaction the credit created, at status initialized, and no balance. A transaction moves through four statuses: initialized when it is recorded, pending once the chain work has been submitted, then completed or failed from what the chain reported. Read it back on the customer’s transaction list, or by its own id, to follow it. An earn requires an idempotency key. A replay of the same key returns the stored response instead of crediting twice, which is exactly the behaviour you want when a request times out and you do not know whether it landed.
POST /gateway/xp/redeem spends a customer’s XP on a reward. It takes the customer and the reward, and the cost comes from the reward rather than from your request. It can be refused in two different ways, and a client that handles only one of them will look broken:
403 with REWARD_TIER_LOCKED when the reward is gated to a tier the customer has not reached. This check runs first, and the cost is never compared when it fails.
400 with XP_INSUFFICIENT_BALANCE when the customer has reached the tier but cannot afford the reward. The response names what was required and what was available.
Handle both. A reward whose tier gate sits at or above its own cost answers a short balance with the 403 and never with the 400. On success it records a transaction the same way an earn does, and the spend leaves the balance when the chain work settles, on the same delay and for the same reason. A balance can also be held: setting its status to locked marks it, and setting it back to active releases it. That is a state you manage, and the customer list reports it.
{"docsConfig":{"theme":"mint","$schema":"https://mintlify.com/docs.json","name":"KINTRA","colors":{"primary":"#35cbff","light":"#5be7ff","dark":"#0b7fa3"},"logo":{"light":"https://mintcdn.com/kintra/aV869_4X610yKJzW/logo/light.png?fit=max\u0026auto=format\u0026n=aV869_4X610yKJzW\u0026q=85\u0026s=d55bc842096a5d67e57c9ab099d58762","dark":"https://mintcdn.com/kintra/aV869_4X610yKJzW/logo/dark.png?fit=max\u0026auto=format\u0026n=aV869_4X610yKJzW\u0026q=85\u0026s=e0a69c1f9fff02d0cacd13175c0b4336"},"favicon":"https://mintcdn.com/kintra/aV869_4X610yKJzW/favicon.ico?fit=max\u0026auto=format\u0026n=aV869_4X610yKJzW\u0026q=85\u0026s=136dbc7fc4ffe42622617d330e1d8f34","api":{"playground":{"display":"interactive"},"examples":{"languages":["curl","javascript","python"],"prefill":true,"autogenerate":true}},"appearance":{"default":"dark","strict":true},"background":{"color":{"dark":"#121212"}},"fonts":{"heading":{"family":"Plus Jakarta Sans"},"body":{"family":"Plus Jakarta Sans"}},"banner":{"content":"Production environment","dismissible":false},"styling":{"latex":false},"navigation":{"pages":[]}},"deployment":{"deploymentMetadataValue":{"subdomain":"kintra","hasAudioNarration":false,"actualSubdomain":"kintra","customDomains":["docs.kintra.io"],"gitSource":{"type":"github","owner":"marco-pellar","repo":"kintra-docs","deployBranch":"main","contentDirectory":"","isPrivate":true,"mountPath":"","label":""},"chroma":{"latestBuiltDeploymentHistoryId":"6a82feba3ea465de84fb99ea","collectionId":"93576bd7-eee2-4565-b6b0-ced101e57af4","liveDeploymentHistoryId":"6a82feba3ea465de84fb99ea","schemaVersion":"v3"},"entitlements":{"AI_CHAT":{"status":"DISABLED"}},"buildId":"6a82feba3ea465de84fb99ea:success","clientVersion":"0.0.3990","searchFilterCounts":[{"language":"default","tag":"API reference","count":94},{"language":"default","tag":"Portal API","count":59},{"language":"default","tag":"Gateway API","count":34},{"language":"default","tag":"Customers","count":24},{"language":"default","tag":"Documentation","count":13},{"language":"default","tag":"Rewards","count":11},{"language":"default","tag":"Tiers","count":11},{"language":"default","tag":"Analytics","count":10},{"language":"default","tag":"Cohorts","count":7},{"language":"default","tag":"Concepts","count":6}]},"assistantConfig":{"enableAskAiButton":false,"enableStarterQuestions":false,"enableContextualStarterQuestions":false,"enableAskAiSearchBar":true},"basePath":"","hasChatPermissions":false,"hasWebMcpPermissions":false,"toggles":[{"name":"kill-isr","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-mintlify-slack","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-v7-file-tree","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-integrations-settings","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-pr-grouping","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-prefix","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"analytics-v7","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"user-flows-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"no-publish-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-onboarding","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"llms-txt-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"pause-universal-search-reconcile","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving-live","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"stripTemperatureMiddleware","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"per-site-search-delta","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"support-platform-pylon-zendesk","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"instant-rollback","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications-server","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-private-page-search","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-fable","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fixed-ai-action-pricing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automation-resolve-merge-conflict","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"scoped-publishing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"mintql","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"page-level-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-version-history-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-nav-generations","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-page-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workspace-projects","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"assistant-v5","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"byok-sep-25","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fork-pr-previews","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"revalidation-jwt","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"vite-origin","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-agent-model-picker","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-updated-9-15","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-sso-scim","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"hobby-abuse-scan","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"dashboard-inbox","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-signup","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-enabled","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false}],"isAtlas":false,"googleFontsInlined":true},"customCss":[{"filePath":"style.css","content":":root{--kintra-code-bg:#0e0e0e;--kintra-code-surface:rgba(255, 255, 255, 0.04);--kintra-code-stroke:rgba(255, 255, 255, 0.08)}pre{background-color:var(--kintra-code-bg);border:1px solid var(--kintra-code-stroke);border-radius:12px}pre code{background-color:transparent;border:0}:not(pre)>code{background-color:var(--kintra-code-surface);border:1px solid var(--kintra-code-stroke);border-radius:6px}"}],"page":{"pagePath":"concepts/xp-and-the-ledger","slug":"/concepts/xp-and-the-ledger","pageMetadata":{"title":"XP and the ledger","description":"XP as a ledger of signed integer movements, what a balance is, and what an earn and a redemption actually do.","href":"/concepts/xp-and-the-ledger"},"description":{"compiledSource":"\"use strict\";\nconst {jsx: _jsx} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = {\n p: \"p\",\n ..._provideComponents(),\n ...props.components\n };\n return _jsx(_components.p, {\n children: \"XP as a ledger of signed integer movements, what a balance is, and what an earn and a redemption actually do.\"\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = {\n ..._provideComponents(),\n ...props.components\n };\n return MDXLayout ? _jsx(MDXLayout, {\n ...props,\n children: _jsx(_createMdxContent, {\n ...props\n })\n }) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"pageType":"default","mdx":{"mdxSource":{"compiledSource":"\"use strict\";const{Fragment:_Fragment,jsx:_jsx,jsxs:_jsxs}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];function _createMdxContent(props){const _components={a:`a`,code:`code`,li:`li`,p:`p`,strong:`strong`,ul:`ul`,..._provideComponents(),...props.components},{Heading,Warning}=_components;if(!Heading)_missingMdxReference(`Heading`,true);if(!Warning)_missingMdxReference(`Warning`,true);return _jsxs(_Fragment,{children:[_jsx(_components.p,{children:`XP is a whole number of points a customer holds under your tenant. Every movement of it is recorded as one transaction, so the ledger is the list of movements and a balance is what those movements have settled to. Two operations move it: an earn credits XP to a customer, and a redemption spends it on a reward you published. `}),`\n`,_jsx(Heading,{level:`2`,id:`amounts-are-signed-integers`,children:`Amounts are signed integers`}),`\n`,_jsx(_components.p,{children:`Model an XP amount as a signed integer in your own code, and never as a floating point value. `}),`\n`,_jsxs(_components.p,{children:[`Two reasons, both practical. The first is direction, and the sign is yours to apply rather than something the API hands you: `,_jsx(_components.code,{children:`amount`}),` comes back as an unsigned magnitude on an earn and on a redemption alike, and `,_jsx(_components.code,{children:`type`}),` is the field that says which way the movement went. `,_jsx(_components.code,{children:`xp_earn`}),` is a credit and `,_jsx(_components.code,{children:`xp_redeem`}),` is a debit, so summing the amounts without reading `,_jsx(_components.code,{children:`type`}),` counts a spend as a credit and overstates the history by twice what was redeemed. `]}),`\n`,_jsxs(_components.p,{children:[`Two more values exist on `,_jsx(_components.code,{children:`type`}),`, `,_jsx(_components.code,{children:`xp_bonus`}),` and `,_jsx(_components.code,{children:`xp_admin_adjust`}),`. Nothing writes either today, so a switch over the four has two branches with no direction to assign yet rather than a miscount waiting to happen. `]}),`\n`,_jsx(_components.p,{children:`The second reason is that the values are exact whole numbers with no smallest fractional unit, because the XP token declares no decimal places, so there is no such thing as part of a point. `}),`\n`,_jsxs(_components.p,{children:[`One thing to filter before you add anything up: a transaction at status `,_jsx(_components.code,{children:`failed`}),` is not a movement. The transaction list applies no status filter unless you ask for one, so a read with no filter hands you `,_jsx(_components.code,{children:`initialized`}),`, `,_jsx(_components.code,{children:`pending`}),` and `,_jsx(_components.code,{children:`failed`}),` rows next to the settled ones, and a naive sum counts a failed redemption as a spend. `]}),`\n`,_jsx(_components.p,{children:`Every amount the API returns is a string, including the balance, and that is deliberate. A balance can grow past the range a floating point number represents exactly, and a client that parses it as one starts losing low digits silently rather than failing. Parse the amounts you read into whatever your language calls a big integer, or keep them as strings and hand them to an integer type at the edge. `}),`\n`,_jsxs(_components.p,{children:[`The request direction is not the same, so do not send a string back. `,_jsx(_components.code,{children:`POST /gateway/xp/earn`}),` declares `,_jsx(_components.code,{children:`amount`}),` as a JSON number: send it unquoted, as a whole number inside the range a JSON number represents exactly. `]}),`\n`,_jsx(Heading,{level:`2`,id:`what-a-balance-is`,children:`What a balance is`}),`\n`,_jsx(_components.p,{children:`A balance is the XP held on chain for that customer, summed across the wallet addresses KINTRA manages for them. It is not a running total that the API increments as writes arrive: the platform reads it back from the token and reports what the token answered. `}),`\n`,_jsxs(_components.p,{children:[`Read one with `,_jsx(_components.code,{children:`GET /gateway/customers/{userId}/xp/balance`}),`, which returns it as `,_jsx(_components.code,{children:`data.balance`}),`. That is the number to show a customer and the number to check before you offer them a redemption. `]}),`\n`,_jsx(_components.p,{children:`The practical consequence of a balance coming from the chain is timing, and it is the one thing about XP worth internalising before you write the integration: `}),`\n`,_jsxs(Warning,{children:[_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`A write does not move the balance.`}),` An earn or a redemption is accepted, recorded, and queued for the chain. The balance changes when that chain work settles and is picked up, which is after the response has already come back to you. `]}),_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`So a read taken immediately after a write can still answer the old number.`}),` That is not an error and retrying the write is the wrong response, because a second earn grants a second time. Treat the response to the write as the receipt that it was accepted, and read the balance when you need to display it. `]})]}),`\n`,_jsx(Heading,{level:`2`,id:`what-an-earn-does`,children:`What an earn does`}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`POST /gateway/xp/earn`}),` credits an amount of XP to a customer, with optional notes recorded against the entry. Call it when the customer does the thing you reward. `]}),`\n`,_jsxs(_components.p,{children:[`The response carries the transaction the credit created, at status `,_jsx(_components.code,{children:`initialized`}),`, and no balance. A transaction moves through four statuses: `,_jsx(_components.code,{children:`initialized`}),` when it is recorded, `,_jsx(_components.code,{children:`pending`}),` once the chain work has been submitted, then `,_jsx(_components.code,{children:`completed`}),` or `,_jsx(_components.code,{children:`failed`}),` from what the chain reported. Read it back on the customer’s transaction list, or by its own id, to follow it. `]}),`\n`,_jsx(_components.p,{children:`An earn requires an idempotency key. A replay of the same key returns the stored response instead of crediting twice, which is exactly the behaviour you want when a request times out and you do not know whether it landed. `}),`\n`,_jsx(Heading,{level:`2`,id:`what-a-redemption-does`,children:`What a redemption does`}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`POST /gateway/xp/redeem`}),` spends a customer’s XP on a reward. It takes the customer and the reward, and the cost comes from the reward rather than from your request. `]}),`\n`,_jsx(_components.p,{children:`It can be refused in two different ways, and a client that handles only one of them will look broken: `}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[`403 with `,_jsx(_components.code,{children:`REWARD_TIER_LOCKED`}),` when the reward is gated to a tier the customer has not reached. This check runs first, and the cost is never compared when it fails. `]}),`\n`,_jsxs(_components.li,{children:[`400 with `,_jsx(_components.code,{children:`XP_INSUFFICIENT_BALANCE`}),` when the customer has reached the tier but cannot afford the reward. The response names what was required and what was available. `]}),`\n`]}),`\n`,_jsx(_components.p,{children:`Handle both. A reward whose tier gate sits at or above its own cost answers a short balance with the 403 and never with the 400. `}),`\n`,_jsx(_components.p,{children:`On success it records a transaction the same way an earn does, and the spend leaves the balance when the chain work settles, on the same delay and for the same reason. `}),`\n`,_jsxs(_components.p,{children:[`A balance can also be held: setting its status to `,_jsx(_components.code,{children:`locked`}),` marks it, and setting it back to `,_jsx(_components.code,{children:`active`}),` releases it. That is a state you manage, and the customer list reports it. `]}),`\n`,_jsx(Heading,{level:`2`,id:`where-to-go-next`,children:`Where to go next`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.a,{href:`/concepts/customers`,children:`Customers`}),` for the record a balance belongs to.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.a,{href:`/concepts/tenants`,children:`Tenants`}),` for why a balance is only ever yours to read.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.a,{href:`/quickstart`,children:`Quickstart`}),` to award XP and read the balance back in two signed requests.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.a,{href:`/conventions`,children:`Request conventions`}),` for the error envelope behind both refusals above.`]}),`\n`]})]})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{default:MDXContent};function _missingMdxReference(id,component){throw new Error(`Expected `+(component?`component`:`object`)+\" `\"+id+\"` to be defined: you likely forgot to import, pass, or provide it.\")}","frontmatter":{},"scope":{"config":{},"pageMetadata":{"title":"XP and the ledger","description":"XP as a ledger of signed integer movements, what a balance is, and what an earn and a redemption actually do.","href":"/concepts/xp-and-the-ledger"}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[{"title":"Amounts are signed integers","slug":"amounts-are-signed-integers","depth":2,"children":[]},{"title":"What a balance is","slug":"what-a-balance-is","depth":2,"children":[]},{"title":"What an earn does","slug":"what-an-earn-does","depth":2,"children":[]},{"title":"What a redemption does","slug":"what-a-redemption-does","depth":2,"children":[]},{"title":"Where to go next","slug":"where-to-go-next","depth":2,"children":[]}],"codeExamples":{}},"jsLinks":[],"subdomain":"kintra","siteName":"KINTRA","title":"XP and the ledger - KINTRA","navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{}},"lastModified":"2026-08-17T12:30:09.647Z","originalFileLocation":"concepts/xp-and-the-ledger.mdx"},"headHtml":"\u003cscript data-mint-head>(function(){var d=document.documentElement,p=\"dark\";if(!p){try{p=localStorage.getItem(\"isDarkMode\")}catch(e){}if(p==='true')p='dark';else if(p==='false')p='light';if(p!=='light'\u0026\u0026p!=='dark'\u0026\u0026p!=='system')p=\"dark\"}var r=p==='system'?(matchMedia(\"(prefers-color-scheme: dark)\").matches?'dark':'light'):p;d.classList.remove(r==='dark'?'light':'dark');d.classList.add(r);d.setAttribute(\"data-theme-preference\",p)})();\u003c/script>\u003cmeta data-mint-head name=\"description\" content=\"XP as a ledger of signed integer movements, what a balance is, and what an earn and a redemption actually do.\">\u003cmeta data-mint-head name=\"application-name\" content=\"KINTRA\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/kintra/ZtFEHcUbLmdYiJ-1/_generated/favicon/android-chrome-192x192.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"/mintlify-assets/_mintlify/favicons/kintra/ZtFEHcUbLmdYiJ-1/_generated/favicon/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/kintra/ZtFEHcUbLmdYiJ-1/_generated/favicon/favicon-16x16.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/kintra/ZtFEHcUbLmdYiJ-1/_generated/favicon/favicon-32x32.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/kintra/ZtFEHcUbLmdYiJ-1/_generated/favicon/favicon.ico\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/kintra/ZtFEHcUbLmdYiJ-1/_generated/favicon-dark/android-chrome-192x192.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/kintra/ZtFEHcUbLmdYiJ-1/_generated/favicon-dark/favicon-16x16.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/kintra/ZtFEHcUbLmdYiJ-1/_generated/favicon-dark/favicon-32x32.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/kintra/ZtFEHcUbLmdYiJ-1/_generated/favicon-dark/favicon.ico\" media=\"(prefers-color-scheme: dark)\">\u003cmeta data-mint-head property=\"og:title\" content=\"XP and the ledger - KINTRA\">\u003cmeta data-mint-head property=\"og:description\" content=\"XP as a ledger of signed integer movements, what a balance is, and what an earn and a redemption actually do.\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"KINTRA\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://docs.kintra.io/concepts/xp-and-the-ledger\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head property=\"og:image\" content=\"https://kintra.mintlify.app/_mintlify/api/og/v2?division=Concepts\u0026amp;title=XP+and+the+ledger\u0026amp;description=XP+as+a+ledger+of+signed+integer+movements%2C+what+a+balance+is%2C+and+what+an+earn+and+a+redemption+actually+do.\u0026amp;theme=7168baa81e08787f570d323b\u0026amp;signature=2f744732d978adb6fff21faef406baa62ceb28dd3bc62cd793d9ca2146b3012f\">\u003cmeta data-mint-head property=\"og:image:width\" content=\"1200\">\u003cmeta data-mint-head property=\"og:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"XP and the ledger - KINTRA\">\u003cmeta data-mint-head name=\"twitter:description\" content=\"XP as a ledger of signed integer movements, what a balance is, and what an earn and a redemption actually do.\">\u003cmeta data-mint-head name=\"twitter:image\" content=\"https://kintra.mintlify.app/_mintlify/api/og/v2?division=Concepts\u0026amp;title=XP+and+the+ledger\u0026amp;description=XP+as+a+ledger+of+signed+integer+movements%2C+what+a+balance+is%2C+and+what+an+earn+and+a+redemption+actually+do.\u0026amp;theme=7168baa81e08787f570d323b\u0026amp;signature=2f744732d978adb6fff21faef406baa62ceb28dd3bc62cd793d9ca2146b3012f\">\u003cmeta data-mint-head name=\"twitter:image:width\" content=\"1200\">\u003cmeta data-mint-head name=\"twitter:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"/mintlify-assets/_mintlify/favicons/kintra/ZtFEHcUbLmdYiJ-1/_generated/favicon/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"KINTRA\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#35cbff\">\u003clink data-mint-head rel=\"canonical\" href=\"https://docs.kintra.io/concepts/xp-and-the-ledger\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/concepts/xp-and-the-ledger.md\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https://docs.kintra.io/#organization\",\"name\":\"KINTRA\",\"url\":\"https://docs.kintra.io\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https://mintcdn.com/kintra/aV869_4X610yKJzW/logo/light.png?fit=max\u0026auto=format\u0026n=aV869_4X610yKJzW\u0026q=85\u0026s=d55bc842096a5d67e57c9ab099d58762\"}},{\"@type\":\"WebSite\",\"@id\":\"https://docs.kintra.io#website\",\"name\":\"KINTRA\",\"url\":\"https://docs.kintra.io\",\"publisher\":{\"@id\":\"https://docs.kintra.io/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https://docs.kintra.io/concepts/xp-and-the-ledger#webpage\",\"url\":\"https://docs.kintra.io/concepts/xp-and-the-ledger\",\"name\":\"XP and the ledger\",\"description\":\"XP as a ledger of signed integer movements, what a balance is, and what an earn and a redemption actually do.\",\"dateModified\":\"2026-08-17T12:30:09.647Z\",\"isPartOf\":{\"@id\":\"https://docs.kintra.io#website\"},\"breadcrumb\":{\"@id\":\"https://docs.kintra.io/concepts/xp-and-the-ledger#breadcrumb\"}},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://docs.kintra.io/concepts/xp-and-the-ledger#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Concepts\",\"item\":\"https://docs.kintra.io/concepts/tenants\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"XP and the ledger\",\"item\":\"https://docs.kintra.io/concepts/xp-and-the-ledger\"}]},{\"@type\":[\"Article\",\"TechArticle\"],\"@id\":\"https://docs.kintra.io/concepts/xp-and-the-ledger#article\",\"headline\":\"XP and the ledger\",\"name\":\"XP and the ledger\",\"description\":\"XP as a ledger of signed integer movements, what a balance is, and what an earn and a redemption actually do.\",\"url\":\"https://docs.kintra.io/concepts/xp-and-the-ledger\",\"mainEntityOfPage\":{\"@id\":\"https://docs.kintra.io/concepts/xp-and-the-ledger#webpage\"},\"dateModified\":\"2026-08-17T12:30:09.647Z\",\"publisher\":{\"@id\":\"https://docs.kintra.io/#organization\"},\"isPartOf\":{\"@id\":\"https://docs.kintra.io#website\"}}]}\u003c/script>\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"KINTRA\",\"creator\":{\"@type\":\"Organization\",\"name\":\"Mintlify\",\"url\":\"https://mintlify.com\"}}\u003c/script>","siteName":"KINTRA","nav":{"global":null,"tabs":[{"tab":"Documentation","groups":[{"group":"Get started","pages":[{"title":"KINTRA","href":""},{"title":"Quickstart","href":"/quickstart"},{"title":"Authentication","href":"/authentication"},{"title":"Request conventions","href":"/conventions"},{"title":"Architecture","href":"/architecture"}]},{"group":"Concepts","pages":[{"title":"Tenants","href":"/concepts/tenants"},{"title":"Customers","description":"Who a customer is in KINTRA's model, how a KINTRA customer maps onto a user you already have, and what a customer record carries.","href":"/concepts/customers"},{"title":"XP and the ledger","href":"/concepts/xp-and-the-ledger"},{"title":"Tiers","description":"How tier membership is computed from a customer's XP balance, why it can move in both directions, and what changes when it does.","href":"/concepts/tiers"},{"title":"Rewards and redemptions","href":"/concepts/rewards-and-redemptions"},{"title":"On-chain XP","href":"/concepts/onchain-xp"}]},{"group":"Walkthroughs","pages":[{"title":"Onboard a customer","href":"/walkthroughs/onboard-a-customer"},{"title":"Award and redeem XP","href":"/walkthroughs/award-and-redeem-xp"}]}]},{"tab":"API reference","pages":[{"title":"API reference","href":"/api-reference/overview"}]}]},"title":"XP and the ledger - KINTRA","buildId":"dpl_2oYXwqkEXirXBPk8wV1FtM5USFWi","deploymentId":"dpl_2oYXwqkEXirXBPk8wV1FtM5USFWi","dynamics":[],"chunks":[]}