Members and points behind the endpoints
“We posted the sale, the member says the points never arrived, and the balance we read back says something else again.” This page is how your code tells which of the four usual answers it is: the document was not the type the company awards on, its lines carried no points, the job that writes them has not run, or your code read the wrong balance endpoint. The six endpoints themselves are documented page by page from the Membership endpoints hub.
What happens after you finalise a sale with a member on it
None of this shows in a response. It decides whether a member ever sees the points.
- The points are on the document line before Finalise, or they are nowhere. At the till the
price book puts
point_amount,point_currencyand the validity dates on each line as it is rung. On a document your own code creates, the line carries points only if you put them there, with one exception: when a cash bill or sales invoice whose header carriesmember_guidis updated or has its posting status changed through the API, the server fills in points for the lines that have none, from a price set that names a Member Class, and only while thePRICEBOOK_POINTS_AUTO_APPLYsetting is on for the POS General or Sales Invoice applet. Other document types are skipped. Points Setup has the setting. A second way to get a figure isPUT core2/tnt/dm/crm/membership/update-points-ccy-amount: send one document-line container and it writes the points from the item’s pricing-scheme point link onto that line and saves the line. That is the pricing scheme’s figure, not the price book’s. - Finalise queues one job and answers. A document you create is stored and numbered and starts
no points job, even when you send it as
FINAL(since mid-October 2026 one sent asFINALis handed to the e-invoice entry, and to nothing else). Finalise is the separate call that queues the primary processor; its200means the document is final and the job is queued. Draft, then Finalise has the two calls. - Which follow-on jobs run is a subscription, decided per tenant and per company. The primary
processor reads the processors subscribed to it for this tenant, drops any the company’s
include or exclude list on its posting configuration rules out, and queues one job each. The
points writer is one of those subscribers, registered as
GENERIC_DOCUMENT_POINT_UPDATE_PROCESSOR. A tenant where it is not subscribed finalises sales that earn nothing, with no error anywhere. - The points writer decides once, and says nothing when it declines. It returns at once when
the document’s membership posting is already
POSTEDorVOID. It then needs three things together: amember_guidon the header, and a document type and posting status equal to the company’sMEMBER_POINTconfiguration, which defaults toINTERNAL_SALES_ORDERatFINALand is set through the company API rather than on a screen (Organisation). Then, for each line that carries bothpoint_amountandpoint_currencyand has no points line yet, it writes one line:REWARDwhen the amount is positive,REDEEMwhen it is negative, validity copied from the document line’s point dates,sourceset to the writer’s own name. It marks the documentPOSTEDonly when it wrote a line or every eligible line already had one. A document with no eligible line is left unmarked, so a later run can award once the lines are fixed. An exception is logged and nothing is raised: Finalise answered200long before. - Each line queues the balance job, to run at once. The job re-reads the line, skips it when it
is deleted or its value is zero, recomputes the member’s stored balance in that currency as the
sum of their active lines, and builds the lot (the control-account row) the expiry run will later
consume. The stored balance is one row per member and point currency in
bl_crm_membership_points_current_balance;current-balance/queryreads it,lines/queryis the ledger behind it, and the lots carry the dates. - Nothing tells you it happened. The
MEMBERSHIP_POINTS_TXN_CREATEDwebhook fires from the header route,POST …/membership-points-txn, and not from the lines the writer creates or fromPOST …/txn-line. An integration that must know a sale earned points querieslines/querywithdoc_hdr_guidafter Finalise; the member-record eventsMEMBERSHIP_CARD_CREATEDandMEMBERSHIP_CARD_UPDATEDdo fire, on the webhook feed.
The member is not the account you bill
GadgetSphere Online’s web store enrols a shopper at checkout: she buys wireless earbuds, joins the
programme, and your integration sends POST core2/tnt/dm/membership-cards with her name, mobile
number, e-mail and identity-card number and no entity_hdr_guid. The backend looks for a customer
by her sign-in, then by the phone, then by the e-mail, and creates an individual customer only when
none matches. A customer it creates carries her name, identity number, phone and e-mail. A customer
it finds keeps whatever identity it already had, and nothing copies hers across.
Three weeks later she orders a laptop for the small company she runs and wants the e-invoice in
the company’s name. Your order carries two different parties on purpose: member_guid is her card,
so the points go to her, and the document’s entity is the company’s trade account, which is billed
and e-invoiced. The e-invoice takes the buyer’s identity from that entity, never from the card. So
the identity number you sent on the member three weeks ago is of no use to this invoice unless it
is also on the customer record, and PUT Update Member will not put it there: the update writes
the membership tables only.
Two things follow for an integration. If your own system is the master of identity and you want the
customer kept in step, enrol through POST and PUT …/membership-cards/with-customer/etl-ep,
which write the member’s name, identity, phone and e-mail onto the linked customer on every create
and update, instead of the plain pair. And before you create, POST …/membership-cards/duplicates/backoffice
with the same container answers the count and the customer records that already share the phone or
e-mail; a shared office number or a family e-mail is exactly how a new member gets linked to somebody
else’s account, so read back entity_hdr_guid after every create and show it to a person when the
check found more than one. Scenario 024 in our planning notes records the support case this was
written from; the same separation is on the membership module page.
What these endpoints will not do
Stated plainly, so you can plan around it.
point_amount and
point_currency earns nothing, and the response does not say so. Put the points on the line, or
rely on the server’s fill-in pass only for a cash bill or sales invoice with the setting on.points_value and nothing else: card_no and membership_hdr_guid are stored as you sent them,
a blank point_currency becomes DEF, and absent validity dates become 1 January 1900 to
31 December 9000, so the lot never expires. The response is 200 in every one of those cases.limit. Paged reads default to 100 rows and pass whatever you send
straight to the query. Count the total only when you need it (calcTotalRecords=true runs a
second query), because a tenant whose connection slots run out answers 429 for everybody.When it goes wrong
| What you see | Usual cause | What to do |
|---|---|---|
403 with code CLIENT_AUTH_USER_NOT_AUTHORIZED on a points write | The key’s user is not an owner or admin and holds none of TNT_LOG_MEMBERSHIP_POINT_TXN_OWNER, TNT_LOG_MEMBERSHIP_POINT_TXN_ADMIN, TNT_API_MEMBERSHIP_POINT_TXN_CREATE | Grant the CREATE permission to the key’s user. The reads want TNT_API_MEMBERSHIP_POINT_TXN_READ, a member create TNT_API_MEMBERSHIP_CARD_CREATE, and the duplicate check TNT_API_ENTITY_READ |
400 with code EXCEPTION_INCONSISTENT_DATA and a list in data | The validator refused the body: a points line without points_value; a member whose card_no or member_id is already taken, or whose entity_hdr_guid does not exist | Read the code on each entry in data; it names the field. This envelope code is not yet on the error index |
400 with code CLIENT_MEMBERSHIP_POINT_CARD_NO_OR_POINT_CURRENCY_NOT_FOUND from current-balance/{cardNo}/{pointCurrency} | No stored balance row for that card in that currency: the member has never earned in it, or the balance job has not run yet | Treat it as zero, or read current-balance/query?card_no=… without a currency, which lists every currency the member holds and answers an empty list otherwise. A line sent without a currency is in DEF |
404 with code OK_NODATA from balance/{card_no} or any login-entity-ep read | The card is not found, or on a login-entity-ep route it is not one of the signed-in user’s own cards | Check the number against membership-cards/query; for a member portal, send the member’s own token |
417 with code SERVER_GENERAL_UNKNOWN_ERROR from current-balance/{cardNo}/{pointCurrency} for one card, while other cards answer | Two balance rows exist for that card and currency. Lines were written with card_no and no membership_hdr_guid, and the balance job keyed a second row on the empty member | Send membership_hdr_guid on every line you write. For the rows already there, ask BigLedger to remove the orphan row, then rebuild the balance from the lines (put the points back the right way) |
200 on POST …/txn-line, and the balance has not moved | points_value was 0, which the job skips; the job is queued to run at once but after your response; or the line went into currency DEF because you sent none | Read again after a moment, and read current-balance/query?card_no=… without a currency to see every currency the member now holds |
The sale is FINAL and lines/query?doc_hdr_guid=… returns nothing | The document is not the type and status the MEMBER_POINT configuration names; the header has no member_guid; the lines carry no points; the writer is not subscribed for the tenant; or it declined and logged | Walk the chain on membership points and vouchers. A document left unmarked is awarded by a later run once its lines are fixed; a document marked POSTED with no lines is the one broken state, and its repair is the data-fix run on that page |
The balance reads zero while lines/query shows the lines | An EXPIRY or insufficient-balance adjustment zeroed the stored figure and the recompute hop was lost | The same page, step 5: re-drive the balance from the lines |
429 with code CLIENT_OVERLOAD_DATABASE | The tenant’s database connection slots are exhausted: large pages with calcTotalRecords=true, or many parallel reads | Smaller pages, no total count unless you use it, and retry with a back-off |
balance/{card_no} disagrees with current-balance | It is one lot row, not a sum, and the read can rewrite it | Read current-balance; read the lots for validity |
Check yourself in thirty seconds
This check was written from the code and has not been run: no demo tenant is available to this desk, and on a customer tenant only the two reads are allowed. One card, one currency, and the arithmetic the balance job itself performs.
- Find the card.
GET …/membership-cards/query?card_no=GS-0001234answers one record; keep itsguid, which is themembership_hdr_guidevery points line should carry. - Read the balance.
GET …/membership-points-txn/etl-ep/current-balance/GS-0001234/GSPTSanswerspoint_balance, or400with the not-found code when the member has never earned inGSPTS; take that as zero. - Write one point, on a test tenant only.
POST …/membership-points-txn/txn-line/etl-epwithpoints_value1,txn_typeADJ,card_no,membership_hdr_guid,point_currencyGSPTSand aremarkssaying what it is. The answer is200with the line’sguid. Read step 2 again: the balance is one higher. Then post the same line withpoints_value-1and read it back to where it was. - Prove the stored figure is the sum of the ledger.
GET …/membership-points-txn/etl-ep/lines/query?card_no=GS-0001234&point_currency=GSPTS&limit=500and addpoints_valueover the lines whosestatusisACTIVE. That sum equalspoint_balancefrom step 2. When it does not, the stored figure is the wrong one, and the repair is on the troubleshooting page, not a line you write by hand.
Members and points in the rest of the product
- What must exist first. A member record, which is where
card_noandmembership_hdr_guidcome from; a point currency, registered undercrm/membership-points-currency; the company’sMEMBER_POINTconfiguration naming the document type and status you actually post; and, unless your code puts the points on every line itself, a price book whose price sets carry a Point treatment and the fill-in setting above. Missing any of these looks the same from your side: a200on Finalise and no line. Rewards Setup has the order. - What becomes possible. Once lines exist, the till offers the stored balance at the next sale,
a member portal can show a member their own balance and history through the
login-entity-eproutes with their own token, and finance can build the month’s points liability from the three figures that should agree (What You Owe Your Members). - The siblings, and when to use which.
POST …/membership-cardsfinds or creates the customer and then leaves it alone;…/with-customer/etl-epwrites the member’s identity onto the customer every time, so choose by which system owns identity.POST …/txn-linewrites one line and raises no webhook;POST …/membership-points-txnwrites a header with its lines and raisesMEMBERSHIP_POINTS_TXN_CREATED; the CSV upload undercrm/membership-points-transaction/import-file-hdrsis for a migration a person checks afterwards.current-balanceis the figure,balance/{card_no}is one lot, and the lot rows are what still counts and until when. The Membership Admin applet does all of this through screens, for a person, and its Add Point Adjustment is the sametxn-lineroute. - What breaks if it is wrong. A hand-written line moves the balance the till offers before your
next request returns, so a wrong
points_valuecan be spent at a counter within the minute; its lot carries the dates you sent, or none, so a mistaken award without adate_valid_tooutlives every genuine one; and because points post to no journal, finance sees it only in the month-end liability figure. A line withoutmembership_hdr_guidbreaks the per-card balance read for that card. On the sales side, a return re-issued to a different account moves the points with the account, and the first party loses them without being told. - What it is used alongside. The till: points earned on bills and spent at the counter are by far
the commonest reward, and where a company has changed its
MEMBER_POINTconfiguration at all it has almost always pointed it at the cash bill. For an integration, the pairing is this page’slines/queryafter Finalise with the member-record webhooks, and the e-invoice buyer block on the document when the member is not the account.