Paprika Peppers

In our household, we use Paprika for storing recipes, managing our grocery lists, and planning our meals each week. We’ve been using it for many many years, love it, and recommend it to anyone who likes to cook. But this post isn’t a review of Paprika. We’re going to take an initial look at how Paprika’s web-services work.

Where does Paprika store your data?

Paprika can be used without a Paprika account if you don’t want to synchronize your data across multiple devices. We share an account so the shopping lists and menus stay in sync. This is good for divide and conquer shopping trips. This also allows us to manage recipes, menus, etc. on our computers, take our phones to the store, and have an iPad in the kitchen for cooking – and have it all be in sync.

So, for synchronization there has be a web-service behind the scenes right? Indeed there is, and Paprika uses this for synchronization, but still stores information locally. And the location isn’t a secret – per Paprika’s own documentation the data on a Windows machine is stored at:

C:\Users\username\AppData\Local\Paprika Recipe Manager 3\Database\

or (for Mac):

/Users/username/Library/Group Containers/72KVKW69K8.com.hindsightlabs.paprika.mac.v3/Data/

Behind the scenes this is just a SQLite database, which we’ll explore in a different post. What we need to know is that our data is available locally AND via API.

v1 vs v2 APIs

I’m not going to go into too much detail here other than to note that there are two versions of the API available – presumably for different versions of the application. I’m going to focus on the v2 variant of the API moving forward, but at a cursory glance the APIs appear to be very similar in resource path and interface structure. As an example, hitting https://www.paprikaapp.com/api/v1/sync/groceries/ vs https://www.paprikaapp.com/api/v2/sync/groceries/ produces the same results. The main difference I noted here is how we authenticate to the different API versions. v1 uses HTTP “Basic” authentication. v2 is a different situation, let’s explore…

v2 Authentication

The v2 API is a bit more sophisticated when it comes to authentication, opting for Bearer Token authentication. The token passed in the Authentication header is something like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3KzgxNzS4RIsImVttWlsIj0ibmNmcml0eTBuY2Z3aTR6Lm5ldCh9.CCNBUFlmUZoIDWRU0Lk-uM-xGe5rUm2efRRt5izkzzQ

For anyone who’s spent sufficient time working with APIs and authentication, the eyJH... should be a dead giveaway. That’s right, this is a JWT token. The decoded token will look something like:

Header:

{
  "alg": "HS256",
  "typ": "JWT"
}

Payload:

{
  "iat": 1738179842,
  "email": "username@email.com"
}

And of course there is the signature, which we won’t worry about. So, so far we have our JWT, and in theory we could use this to make API calls… neat! What’s interesting about the JWT here is it just contains the Issued At (iat) claim and an email claim. These tokens are good forever… which is convenient for building a client, but not particularly great from a security standpoint, but then again, we’re dealing with recipes and grocery lists, not banking details.

If we wanted to build an API client, we would just need to supply a valid JWT token and we’d be good to go. I captured tokens from Paprika using Proxyman, which works, but it’s (1) a bit of a pain to do this, and (2) we should know how to authenticate to get a token, so, let’s do that.

Obtaining a new token

To obtain a new token, we POST to https://www.paprikaapp.com/api/v2/account/login/. The full set of headers is as follows:

Host: www.paprikaapp.com
Content-Type: application/x-www-form-urlencoded; charset=utf-8
Connection: keep-alive
Accept: /
User-Agent: Paprika Recipe Manager 3/3.8.1 (com.hindsightlabs.paprika.mac.v3; build:38; macOS 15.2.0) Alamofire/5.2.2
Accept-Language: en-US;q=1.0
Content-Length: 6660
Accept-Encoding: br;q=1.0, gzip;q=0.9, deflate;q=0.8

And the form data contains three fields: “email”, “password”, and “receipt”. The response data will contain your issued JWT:

{
  "result": {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3KzgxNzS4RIsImVttWlsIj0ibmNmcml0eTBuY2Z3aTR6Lm5ldCh9.CCNBUFlmUZoIDWRU0Lk-uM-xGe5rUm2efRRt5izkzzQ"
  }
}

Now, you may be wondering what the heck the “receipt” field is. Well, this is a digital receipt that is issued by Apple’s App Store. The data is base64 encoded as the file is not human readable. This file gets stored with the application upon opening the app and verifying the purchase with the App Store. As I’m not using a Windows machine, I don’t know if the login process is the same there (presumably it is) but I don’t know if “receipt” is needed there or how to obtain it. For Mac, I can tell you how to find this. Let’s take a look.

One more note on calling the API – If you make a request using a non-standard User-Agent string you may get an unknown client error. This occurred when I was testing with Postman and using the default UA string. Using the Paprika specific UA worked, as well as a well known browser UA.

The App Store receipt

Ok, so where is the receipt? As mentioned previously this gets stored with the application bundle. The specific location is:
/Applications/Paprika Recipe Manager 3.app/Contents/_MASReceipt/receipt. The file itself isn’t directly readable, but looking at the contents we can see some information about Apple’s CAs, CRL URLs, and other such goodies. Now, we can simply base64 encode the content of the file, make our login request and get our JWT, but I wanted to know a bit more about this file. To verify this and get details on the receipt we can POST to https://buy.itunes.apple.com/verifyReceipt:

curl -d '{ "receipt-data": "base64 encoded contents of receipt" }' https://buy.itunes.apple.com/verifyReceipt

And the response should be something like:

{
  "receipt": {
    "receipt_type": "Production",
    "adam_id": 1603222728,
    "app_item_id": 1603222728,
    "bundle_id": "com.hindsightlabs.paprika.mac.v3",
    "application_version": "3.8.1",
    "download_id": 36037034586717,
    "version_external_identifier": 871503611,
    "receipt_creation_date": "2025-01-22 03:50:48 Etc/GMT",
    "receipt_creation_date_ms": "1737517848000",
    "receipt_creation_date_pst": "2025-01-21 19:50:48 America/Los_Angeles",
    "request_date": "2025-01-30 02:25:55 Etc/GMT",
    "request_date_ms": "1738203955578",
    "request_date_pst": "2025-01-29 18:25:55 America/Los_Angeles",
    "original_purchase_date": "2017-12-23 16:28:22 Etc/GMT",
    "original_purchase_date_ms": "1514046502000",
    "original_purchase_date_pst": "2017-12-23 08:28:22 America/Los_Angeles",
    "original_application_version": "3.2.0",
    "in_app": []
  },
  "environment": "Production",
  "status": 0
}

That’s cool, but doesn’t have any bearing on the actual auth to Paprika’s API, but curious minds want to know.

ncfritz Avatar

Published by

Categories:

Leave a comment