Why Would Anyone Do This?
In my investigation of Spenderlog, I decompiled a "free spending tracker" and found it was actually a market research data collection tool by DVJ Insights. The app worked by taking your Trumf, Coop, and Rema 1000 credentials and scraping your receipts on your behalf.
That raised an obvious follow-up question: what APIs are these grocery apps actually using? Could you build your own receipt aggregator — one that doesn't ship your purchase history to a Dutch market research agency?
So I pulled the APKs for Rema 1000 and Coop Norway, decompiled them, and mapped every API endpoint. Here's what I found.
Full API documentation, including OpenAPI specs: GitHub Gist
The Three Apps
Norway's grocery market is dominated by three groups:
- NorgesGruppen (Kiwi, Meny, Spar, Joker) — loyalty program: Trumf
- Coop (Coop Extra, Coop Prix, Coop Mega, Obs) — app: Coop Medlem
- Rema 1000 — loyalty app: Æ
Together they cover ~97% of Norwegian grocery retail. Each has an app with purchase history and digital receipts. Each app talks to a backend API that's not publicly documented.
Rema 1000: The Easy One
Package: no.rema.bella | Version: 3.0.12 | Tech: Kotlin, Retrofit2, OkHttp3
Rema's app is a native Android app built with Kotlin. Decompiling it with jadx gives you clean, readable source code with every API endpoint neatly defined as Retrofit interface methods.
How I Decompiled It
# Download the APK
apkeep -a no.rema.bella -d apk-pure ./rema-apk/
# Decompile
jadx -d decompiled/ rema.apk
# The API interfaces are right here:
ls decompiled/sources/no/shortcut/bella/data/remote/api/
The app is developed by Shortcut (a Norwegian digital agency), uses Koin for dependency injection, Jetpack Compose for the UI, and Retrofit2 + OkHttp3 for networking.
Authentication
Rema uses OAuth 2.0 Authorization Code with PKCE, via their own identity provider at id.rema.no:
| Parameter | Value |
|---|---|
| Authorization | https://id.rema.no/authorization |
| Token | https://id.rema.no/token |
| Client ID | android-251010 |
| Scope | all |
| PKCE | Yes (CodeVerifier) |
Every API request requires two key headers:
Authorization: Bearer <access_token>
ocp-apim-subscription-key: fb5e24884b504d0bad761098f77e6605
The second header is an Azure API Management subscription key — Rema's backend runs on Azure. The key is hardcoded in the APK.
The Receipt API
The two endpoints that matter:
List all purchases:
GET https://api.rema.no/v1/bella/transaction/v2/heads
Returns every purchase with store name, date, amount, and a transaction ID:
{
"bonusTotal": 0,
"purchaseTotal": 4250.80,
"discountTotal": 312.50,
"transactions": [
{
"purchaseDate": 1600695669000,
"storeId": "7080",
"amount": 249.90,
"storeName": "Rema 1000 Storgate",
"id": 11223344556,
"receiptNbr": "2009210000123123123123123"
}
]
}
Get line items for a receipt:
GET https://api.rema.no/v1/bella/transaction/v2/rows/{transactionId}
Returns every item on the receipt with EAN barcode, price, and discounts:
[
{
"prodtxt1": "NORVEGIA 1KG",
"prodtxt3": "7038010009457",
"productGroupDescription": "Ost",
"unitPrice": 109.90,
"amount": 109.90,
"discount": 20.00
}
]
The prodtxt3 field is the EAN-13 barcode — a globally unique product identifier. This is exactly what Spenderlog
extracts and sends to DVJ Insights.
What Else Is In There
Beyond receipts, the decompiled source reveals the full API surface — 50+ endpoints across transactions, offers,
customer profile, shopping lists (with real-time WebSocket sync), Scan & Pay self-checkout, payment cards, Vipps
integration, geolocation, product search by GTIN, and even GDPR data access requests (/v1/sardar/DataAccessRequest).
The full list is in the OpenAPI spec.
Security Notes
- No certificate pinning. No
CertificatePinner, nonetwork_security_config.xml. All traffic is interceptable with a standard mitmproxy setup. - Tokens are stored in
EncryptedSharedPreferences. - Mutating operations require a sync token from
HEAD /synctoken.
Coop Norway: The Hard One
Package: no.coop.members | Version: 4.17.3 | Tech: Flutter (Dart → ARM32)
Coop's app is built with Flutter. This changes everything about the reverse engineering approach.
Why Flutter Is Different
When you decompile a native Kotlin/Java app with jadx, you get readable source code. When you decompile a Flutter app, you get:
- Java/Kotlin side: A thin shell —
MainActivity, Flutter engine initialization, and platform channel bridges. No business logic. libapp.so: A 19 MB compiled ARM binary containing all the Dart code. Not decompilable to readable Dart.libflutter.so: The Flutter engine itself (8 MB), including BoringSSL for certificate pinning.
You can't read the Dart source code. But you can extract every string literal from the binary.
The strings Approach
# Extract libapp.so from the XAPK split APK
unzip config.armeabi_v7a.apk "lib/armeabi-v7a/libapp.so"
# Extract all URLs
strings libapp.so | grep -E "^https?://" | sort -u
# Extract API paths
strings libapp.so | grep -E "^/user/" | sort -u
# Extract data model names
strings libapp.so | grep -E "^TPurchase" | sort -u
# Extract JSON field names
strings libapp.so | grep -E "^(receiptId|storeName|ean13|amount)" | sort -u
# Extract gRPC service paths
strings libapp.so | grep -E "^/coopnorge\." | sort -u
This works because Dart string literals survive compilation. Method names, class names, JSON serialization keys, URL constants, error messages — they're all in there as plain ASCII.
What I Found
Base URL: https://api.coop.no
Authentication: OpenID Connect via Auth0 at https://login.coop.no/. I confirmed this by fetching the well-known
configuration:
curl -s https://login.coop.no/.well-known/openid-configuration | jq .issuer
# "https://login.coop.no/"
Auth0-hosted OIDC with PKCE (S256), supporting scopes like openid, profile, email, phone, offline_access.
Payment auth (Coopay): Aera SDK from Giant Leap at https://api.aerahost.com/ — handles BankID verification and
payment signing via biometrics.
The Receipt Endpoints
GET /user/pay/history/dashboard → Spending overview
GET /user/pay/history/list → Purchase list
GET /user/pay/history/month → Monthly breakdown
GET /user/pay/history/details → Full receipt with line items
GET /user/pay/history/search → Search by product/store
GET /user/pay/history/receipt.pdf → Download receipt as PDF
The data model, reconstructed from Dart class names and JSON field names:
PurchaseSummary {
summaryId, receiptId, purchaseDate,
storeName, storeId, chainId,
amount, totalDiscount, memberBonus,
lines: PurchaseSummaryLine[]
}
PurchaseSummaryLine {
productName, ean13, gtin13, barcode,
quantity, amount, discount, unitPrice
}
Beyond Receipts
The strings extraction revealed 70+ API endpoints covering:
- Coupons:
/coupon/all,/coupon/activate,/coupon/swap - Coopay (mobile payment):
/user/pay/activate,/user/pay/devices,/user/pay/scancodes - Shop Express (scan & go):
/user/shopexpress/shoppingtrip/init,/user/shopexpress/shoppingtrip/add_cart_item - Family:
/user/family/myfamily,/user/family/send_invitation - Parking:
/user/parking/history,/user/vehicle/list - Mastercard:
/user/mastercard/card-info,/user/mastercard/movements - BankAxept:
/user/bankaxept/enrollment - gRPC Shopping Lists at
handleliste.coop.no(protobuf over HTTP/2)
Full list in the OpenAPI spec.
The XAPK Problem
One complication: the Coop APK from APKPure only ships with armeabi-v7a (32-bit ARM) native libraries. Modern
Android emulators on Apple Silicon are 64-bit only and dropped 32-bit support. This means you can't easily run the
app in an emulator for dynamic analysis.
Options for live traffic interception:
- Physical Android phone — plug in via USB, run Frida. Works immediately.
- Older Android emulator image (Android 12 or below) — still supports 32-bit.
- Repack the APK — inject
libapp.sointo the base APK, removeisSplitRequired, re-sign. - Skip it entirely — the
stringsapproach gives you 95% of what you need without running the app.
Trumf / NorgesGruppen
Trumf is already documented by the community. ttyridal/trumf-data-fetch is a working Python script that:
- Authenticates with phone + password
- Fetches transactions from
https://platform-rest-prod.ngdata.no/trumf/husstand/transaksjoner - Fetches line items from
/trumf/husstand/transaksjoner/detaljer/{batchid}
Each transaction includes Norwegian field names (dato, beskrivelse, belop, trumf) and line items with
vareTekst, ean, antall, belop.
Building Your Own Receipt Fetcher
With all three APIs mapped, you could build a self-hosted receipt aggregator. Here's the approach:
For Rema 1000
The API is the most accessible. No certificate pinning, clean REST endpoints, well-documented from decompilation.
- Implement OAuth 2.0 PKCE flow against
id.rema.no - Include the subscription key header on every request
- Call
/v1/bella/transaction/v2/headsto list purchases - Call
/v1/bella/transaction/v2/rows/{id}for line items - Store product data using
prodtxt3(EAN) as the key
An existing Node.js wrapper already exists: Starefossen/node-rema-ae-api.
For Coop Norway
More work needed — the exact request/response format hasn't been verified via live interception. But the endpoints and data model are mapped:
- Implement OIDC flow against
login.coop.no(standard Auth0) - Call
/user/pay/history/listfor purchase stubs - Call
/user/pay/history/detailsfor full receipts with line items - Or download PDFs via
/user/pay/history/receipt.pdf?receiptid=
For Trumf
Use trumf-data-fetch directly — it's a working implementation.
The Bigger Picture
What Spenderlog does — collecting receipt data from multiple grocery chains and aggregating it — is technically straightforward. The APIs exist. The data is structured. The authentication is standard OAuth/OIDC.
The question isn't whether you can build this. It's whether you should, and who benefits when you do.
When you build it for yourself, you get a spending tracker that keeps your data on your own infrastructure. When DVJ Insights builds it and advertises it as "100% free," Norwegian households get a spending chart and a market research agency gets product-level purchase data from 4,000-5,000 households to sell to FMCG brands.
Same APIs. Same data. Very different business models.
Resources:
- Full API documentation (Gist)
- Rema 1000 OpenAPI Spec
- Coop Norway OpenAPI Spec
- Spenderlog Investigation
Found this useful? Have corrections or additions? Reach out on Twitter/X.
