Skip to content

Instantly share code, notes, and snippets.

@mattdsteele
Last active March 23, 2024 16:55
Show Gist options
  • Star 109 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save mattdsteele/7386ec363badfdeaad05a418b9a1f30a to your computer and use it in GitHub Desktop.
Save mattdsteele/7386ec363badfdeaad05a418b9a1f30a to your computer and use it in GitHub Desktop.

Paprika doesn't have their API documented, so this is me reverse-engineering it from an Android device

Live Demo

https://knowing-grain.glitch.me/

Code: https://glitch.com/edit/#!/knowing-grain

Sync

HTTP BASIC auth (emoji shrug), and send a GET to:

Syncing a particular recipe:

API uses BASIC auth; whatever your cloud sync is.

Services do not have CORS headers, so you can't invoke them directly from a browser :(

Saving/Updating a recipe

Send a POST to: https://www.paprikaapp.com/api/v1/sync/recipe/{uid-of-recipe}/

With your recipe in a multipart/form-data, in the data param. Example:

{
  "uid": "ccb42915-5fe9-425d-98da-c1ffbe420159",
  "name": "Test",
  "directions": "Do",
  "servings": "",
  "rating": 0,
  "difficulty": "Easy",
  "ingredients": "Ddddjdjdd\nDjdjdjdd\nDjdhdhdhdndnee ",
  "notes": "",
  "created": "2018-03-26 09:00:02",
  "image_url": null,
  "on_favorites": 0,
  "cook_time": "",
  "prep_time": "",
  "source": "",
  "source_url": "",
  "photo_hash": null,
  "photo": null,
  "nutritional_info": "",
  "scale": null,
  "deleted": false,
  "categories": [
    "cbaca738-cdfb-4150-960d-e1b1ac4cdcc3"
  ],
  "hash": "162e5ad0134e9398b98057aea951304780d0396582238320c28b34a7c35f841e"
}

The data param should be gzip-encoded

"Save Recipe" function

Still figuring this out 🤷‍♂️

Appears to be a POST to https://www.paprikaapp.com/api/v1/sync/recipes/, no auth

Sends a multipart/form-data with three fields:

Returns a text-plain (actually JSON) with a structure like:

{
    "result": {
        "cook_time": "4 mins. to 8 mins.",
        "difficulty": "",
        "directions": "To make the mix: Grind the oats in a food processor until they're chopped fine, but not a
powder.\n\nPut the flour, oats, and all other dry ingredients into a mixer with a paddle. Mix on slow speed, and
drizzle the vegetable oil into the bowl slowly while the mixer is running.\n\nStore in an airtight container for up
to two weeks at room temperature, or indefinitely in the refrigerator or freezer.\n\nTo make pancakes: Whisk
together 1 cup of mix, 1 cup of buttermilk (or a combination of half plain yogurt and half milk; or 3/4 cup liquid
whey), and 1 large egg. Don't worry if it seems thin at first: the oats will soak up the milk, and the mix will
thicken a bit as it stands.\n\nLet the batter stand for at least 20 minutes before cooking.\n\nHeat a lightly
greased griddle to 350°F (if you've got a griddle with a temperature setting; if not, medium-hot will do).\n\nDrop
the batter onto it in 1/4-cupfuls (a jumbo cookie scoop works well here) to make a 4\" diameter pancake. If you
have English muffin rings, use them; they make a perfectly round, evenly thick pancake.\n\nWhen the edges look dry
and bubbles come to the surface without breaking (after about 2 minutes, if your griddle is the correct
temperature), turn the pancake over to finish cooking on the second side, which will take about 2 minutes.\n\nServe
pancakes immediately, or stack and hold in a warm oven.\n\nYield: a batch using 1 cup of the mix will make about 5
to 8 pancakes, depending on size.",
        "image_url": "",
        "ingredients": "MIX\n4 cups King Arthur White Whole Wheat Flour or Organic White Whole Wheat Flour\n1 cup
King Arthur Unbleached All-Purpose Flour or Organic All-Purpose Flour\n3 1/2 cups old-fashioned or rolled oats\n3
tablespoons sugar\n3 tablespoons baking powder\n1 tablespoon salt\n1 tablespoon baking soda\n1 cup vegetable
oil\nPANCAKES\n1 cup homemade mix\n1 cup buttermilk, nut milk, or a combination of plain yogurt and milk; or 3/4
cup liquid whey\n1 large egg",
        "name": "Homemade Whole-Grain Pancake Mix",
        "notes": "",
        "nutritional_info": "Calories: 110\nTotal Carbohydrates: 12g\nCholesterol: 30mg\nTotal Fat: 5g\nDietary
fiber: 3g\nProtein: 4g\nSaturated fat: 1g\nAmount Per: 1 pancake (56g)\nSodium: 260mg\nSugar: 3g\nTrans Fat: 0g",
        "prep_time": "20 mins.",
        "servings": "10 cups dry mix",
        "total_time": ""
    }
}

You can then convert it to a recipe by making a UID and POSTing it to the URLs above

@ThiefMaster
Copy link

Sending a POST to /api/v2/sync/notify/ results in currently-running clients to immediately check for updates. I'm not sure what channel it uses for the push notification, but the client sends a GET request to /api/v2/sync/status/ which returns a JSON structure like this:

{
  "result": {
    "menus": 0,
    "photos": 13,
    "mealtypes": 12,
    "recipes": 74,
    "pantry": 0,
    "meals": 0,
    "groceryingredients": 0,
    "groceries": 0,
    "groceryaisles": 71,
    "grocerylists": 3,
    "bookmarks": 0,
    "menuitems": 0,
    "categories": 15
  }
}

I first thought those are the number of recipes etc, but this isn't the case: The counter is updated whenever you make a change. So the client then checks which counters have been incremented since the last sync and then start fetching data for those types.

@cmchap
Copy link

cmchap commented Jun 4, 2020

My goal is to add a new recipe with multiple photos, and with photos showing up within the directions. The MacOS app can do this, and I assume it's using the API somehow.

What I've learned so far:
With an appropriate Bearer Token, sending a POST to /api/v2/sync/photos returns a list of photos like this:

{
    "result": [
        {
            "hash": "D3A5DBC0F178563AA3F1962013FC4A6A45257654AA4F2EDAFE796D484B6CE726",
            "uid": "1CD2739F-A8DD-474F-962D-0AC831B1ACAF-51134-00025FC59C659AFC",
            "order_flag": 0,
            "recipe_uid": "07DC5702-0D52-42FD-9C7C-6E6A2384ABC6-7659-000068021E6B6C05",
            "filename": "1CD2739F-A8DD-474F-962D-0AC831B1ACAF-51134-00025FC59C659AFC.jpg",
            "name": "cory_green"
        },
        {
            "hash": "4424A7B1BD9C37F8B5C384DD591D96BCAE19E9B5D383E49867E227964FA65137",
            "uid": "FA8A9BD9-CD9C-4798-8D9A-957088724339-51134-00025FC6B362BBAA",
            "order_flag": 1,
            "recipe_uid": "07DC5702-0D52-42FD-9C7C-6E6A2384ABC6-7659-000068021E6B6C05",
            "filename": "FA8A9BD9-CD9C-4798-8D9A-957088724339-51134-00025FC6B362BBAA.jpg",
            "name": "cory_blue"
        }
    ]
}

And sending a POST to /api/v2/sync/photo/*uid*returns info on that photo like this:

{
    "result": {
        "hash": "D3A5DBC0F178563AA3F1962013FC4A6A45257654AA4F2EDAFE796D484B6CE726",
        "uid": "1CD2739F-A8DD-474F-962D-0AC831B1ACAF-51134-00025FC59C659AFC",
        "order_flag": 0,
        "recipe_uid": "07DC5702-0D52-42FD-9C7C-6E6A2384ABC6-7659-000068021E6B6C05",
        "photo_url": "http://uploads.paprikaapp.com.s3.amazonaws.com/463755/07DC5702-0D52-42FD-9C7C-6E6A2384ABC6-7659-000068021E6B6C05/1CD2739F-A8DD-474F-962D-0AC831B1ACAF-51134-00025FC59C659AFC.jpg?Signature=jRcNj4wVrdrONKHVmWz1riE0zIA%3D&Expires=1591292943&AWSAccessKeyId=AKIAQ656ARCHKEP3RMYH",
        "filename": "1CD2739F-A8DD-474F-962D-0AC831B1ACAF-51134-00025FC59C659AFC.jpg",
        "name": "cory_green"
    }
}

@ThiefMaster
Copy link

ThiefMaster commented Jun 4, 2020

Here's some code I'm successfully using to save recipes with photos.

It's taken from a tool I wrote to sync recipes between to accounts, but it shouldn't be too hard to populate those objects with fresh data instead.

API_BASE = 'https://www.paprikaapp.com/api/v2'
SYNC_RECIPE_URL = lambda uid: f'{API_BASE}/sync/recipe/{uid}/'  # noqa:E731
SYNC_PHOTOS_URL = f'{API_BASE}/sync/photos/'
SYNC_PHOTO_URL = lambda uid: f'{API_BASE}/sync/photo/{uid}/'  # noqa:E731


@dataclass_json()
@dataclass
class Photo:
    uid: str
    filename: str
    name: str
    order_flag: int
    recipe_uid: str
    hash: str
    photo_url: Optional[str] = None
    deleted: bool = False

    def get_photo_data(self):
        if not self.photo_url:
            # we only have a url if we loaded this photo specifically using
            # its own dedicated url, not when we got just the whole list
            return None
        resp = requests.get(self.photo_url)
        resp.raise_for_status()
        return resp.content

    def save(self, token: str):
        files = {'data': _gzip(self)}
        photo_data = self.get_photo_data()
        if photo_data:
            # self.photo is the filename
            files['photo_upload'] = (self.filename, photo_data)
        resp = requests.post(
            SYNC_PHOTO_URL(self.uid), files=files, headers=_auth(token)
        )
        resp.raise_for_status()
        error = resp.json().get('error')
        if error:
            raise RequestFailed(error)


@dataclass_json()
@dataclass
class Recipe:
    categories: List[str]
    cook_time: str
    created: str
    description: str
    difficulty: str
    directions: str
    hash: str
    image_url: Optional[str]
    in_trash: bool
    ingredients: str
    is_pinned: bool
    name: str
    notes: str
    nutritional_info: str
    on_favorites: bool
    on_grocery_list: Optional[str]
    photo: Optional[str]
    photo_hash: Optional[str]
    photo_large: Optional[str]
    photo_url: Optional[str]
    prep_time: str
    rating: int
    scale: Optional[str]
    servings: str
    source: str
    source_url: str
    total_time: str
    uid: str

    def clear_user_data(self):
        self.categories = []
        self.on_grocery_list = False

    def get_photo_data(self):
        if not self.photo or not self.photo_url:
            return None
        resp = requests.get(self.photo_url)
        resp.raise_for_status()
        return resp.content

    def save(self, token: str):
        files = {'data': _gzip(self)}
        photo_data = self.get_photo_data()
        if photo_data:
            # self.photo is the filename
            files['photo_upload'] = (self.photo, photo_data)
        resp = requests.post(
            SYNC_RECIPE_URL(self.uid), files=files, headers=_auth(token)
        )
        resp.raise_for_status()
        error = resp.json().get('error')
        if error:
            raise RequestFailed(error)

and

recipe.save(token)
for photo in photos:
    photo = paprika.get_photo(partner.token, photo.uid)
    photo.save(token)

get_photo gets the photo from the photo-specific URL, but in your case simply build the same kind of data yourself:

def get_photo(token: str, uid: str) -> Photo:
    resp = requests.get(SYNC_PHOTO_URL(uid), headers=_auth(token))
    resp.raise_for_status()
    data = resp.json()
    return Photo.from_dict(data['result'])

The easiest way to reverse engineer this is to use Fiddler or a similar tool to look at the data the real app sends when you create a new recipe with photos etc. It's gzipped in the POST payload but it's super easy to unpack it using various tools. In my case I used Fiddler and selected the payload from the multipart form data where the gzip header starts (1f 8b), and up until the end (some NUL bytes that are followed by a CRLF and the mime multipart separator), saved the raw binary data to a file, and useg gzcat < thatfile.gz to view the unpacked version.

@coddingtonbear
Copy link

Initially, I put this command-line app & library together for working with the paprika app's recipe export files, but after coming across this thread, I used the extremely helpful information here to add support for editing/managing your recipe collection -> https://github.com/coddingtonbear/paprika-recipes ; just in case any of you folks are looking for something like that.

@ThiefMaster
Copy link

cool! reminds me that i should probably finish my web-based tool at some point...

@ekiczek
Copy link

ekiczek commented Nov 14, 2020

I wanted a simple list of recipes from Paprika so I made this quick Python script: https://github.com/ekiczek/get_paprika_recipes_list. I hope others find it helpful.

@datapolitical
Copy link

Thanks to @sstarcher this now supports syncing all photos: https://github.com/datapolitical/paprika-exporter/tree/v0.2.0

@sstarcher
Copy link

@datapolitical any interest in putting in a PR back to my project

@dunhamsteve
Copy link

For the hash on a recipe, it looks like there is a method SyncEntity.GenerateHash() that just generates a UUID and calculates a SHA256 hash of it. (I peeked at the android version in a disassembler.) So any random hash should be fine.

@datapolitical
Copy link

@datapolitical any interest in putting in a PR back to my project

Just saw this. Yeah I’ll try to get to it this weekend.

@jhmleung
Copy link

jhmleung commented Jul 15, 2021

Could anyone provide more context on how to add a grocery/menu? i'm getting an Invalid Data response when I post but difficult to track down what each field requires. how did you get the order_flag number @psi

i'm hoping to make a meal-plan generator, would also be useful to know the data for posting a menu if possible

@datapolitical
Copy link

Sending a POST to /api/v2/sync/notify/ results in currently-running clients to immediately check for updates. I'm not sure what channel it uses for the push notification, but the client sends a GET request to /api/v2/sync/status/ which returns a JSON structure like this:

{
  "result": {
    "menus": 0,
    "photos": 13,
    "mealtypes": 12,
    "recipes": 74,
    "pantry": 0,
    "meals": 0,
    "groceryingredients": 0,
    "groceries": 0,
    "groceryaisles": 71,
    "grocerylists": 3,
    "bookmarks": 0,
    "menuitems": 0,
    "categories": 15
  }
}

I first thought those are the number of recipes etc, but this isn't the case: The counter is updated whenever you make a change. So the client then checks which counters have been incremented since the last sync and then start fetching data for those types.

This was hugely helpful @ThiefMaster! Thank you!

@cskimm01
Copy link

cskimm01 commented Oct 4, 2021

Could anyone provide more context on how to add a grocery/menu? i'm getting an Invalid Data response when I post but difficult to track down what each field requires. how did you get the order_flag number @psi

i'm hoping to make a meal-plan generator, would also be useful to know the data for posting a menu if possible

I think we are working on a similar project! If you wouldn't mind sharing if you find a way to poll/post to the menus, that woud be awesome.
(I am a super n00b, if that's not already apparent) this is one of my first projects, and at this point I just need something to tel me what to eat.

@datapolitical
Copy link

datapolitical commented Oct 4, 2021

@datapolitical any interest in putting in a PR back to my project

Finally got around to this @sstarcher. The only issue is that I’ve changed the directory structure of the project because I needed to use it as a python package so I could run the code easily in other repos.

@8bitgentleman
Copy link

Anyone having any success with POST ing to the "Save Recipe" /recipes endpoint? Haven't been able to get it to work

@datapolitical
Copy link

I haven’t tried, but I know that a couple of libraries have successfully implemented it.

I would take a look at how this does it: https://github.com/coddingtonbear/paprika-recipes

@8bitgentleman
Copy link

I haven’t tried, but I know that a couple of libraries have successfully implemented it.

I would take a look at how this does it: https://github.com/coddingtonbear/paprika-recipes

Hey @datapolitical looks like that library does allow you to create a recipe but it's a manual process. I'm looking for the ability to send it a URL for a recipe webpage and have it return the parsed recipe information, as the description mentions. Have you seen anything that supports that?

@datapolitical
Copy link

No, because those are two separate problems. First you have to take a webpage and parse it to extract the recipe data. And then you have to push that to Paprika.

and the problem is that while some websites have structured data to make it easier for recipe tools to understand them, many do not.

and you don’t necessarily want to push that data into your Paprika without checking it first.

but tools do exist. Like this one: https://schollz.com/blog/ingredients/

you’ll just have to take the output of that and pass it to The paprika API. but I would not expect that to work particularly well. These guys do the same thing, but it only works on webpages they have previously prepared it to work with: https://github.com/hhursev/recipe-scrapers

@8bitgentleman
Copy link

Sure that's 2 separate problems but it's also something that paprika can already do, both in the app and with the bookmarklet. I've sent dozens of recipes to paprika, many from obscure websites, and it's never had any trouble picking out the recipe on its own with minimal to no effort from me. If this is not something that the Paprika API exposes that's fine but the way I read the writeup above it seems as though it is exposed. Ideally one would

  1. Grab a webpage's HTML (I took a look at the bookmarklet code which is similar and all it does is grab everything under the pages' <HTML> )
  2. Grab a webpage's CSS
  3. POST both along with the URL to the paprika /recipes endpoint
  4. The API returns structured JSON which could then be sent back to Paprika with a UID or used wherever

@datapolitical
Copy link

datapolitical commented Oct 4, 2021 via email

@SlowSpeedChase
Copy link

Does anyone use the pantry feature of the app? I was hoping to find an API for it - I want to script Paprika to import pantry data from an app like PantryCheck. It would be so much easier to manage determining what I need to purchase vs what I have on hand

@targetdrone
Copy link

@SlowSpeedChase , read the above post on how to add/update/delete the categories. Modifying the pantry is the same as modifying a category, but you need to POST it to the /api/v2/sync/pantry endpoint, following the pantry schema.

As with categories, you need to generate a unique uid value. The only required fields are uid, ingredient, and aisle. Here's the schema:

[{
"uid": "8978d315-e574-4e81-8ded-4da9b9d42927",
"ingredient": "onion powder",
"aisle": "Spices and Seasonings",
"expiration_date": null,
"has_expiration": false,
"in_stock": false,
"purchase_date": null,
"quantity": null,
"aisle_uid": null
}]

@xraywinedrinker
Copy link

@SlowSpeedChase, were you able to figure out how to script the Paprika pantry data? If so, would you mind sharing your method?

@jschieck
Copy link

took some digging around but here's how you can add/update grocery list items. i would imagine that pantry items work the same way, but haven't tried it.

i've successfully got this all working inside my custom google home action inside a google cloud webhook "hey google, ask paprika to add 5 eggs to grocery list"

GET grocerylists

var request = require('request');
var options = {
  'method': 'GET',
  'url': 'https://www.paprikaapp.com/api/v2/sync/grocerylists/',
  'headers': {
    'Authorization': 'Bearer YOUR_TOKEN'
  }
};
request(options, function (error, response) {
  if (error) throw new Error(error);
  console.log(response.body);
});
{
    "result": [
        {
            "uid": "XXX",
            "name": "Publix",
            "order_flag": 4,
            "is_default": false,
            "reminders_list": "Publix"
        },
        {
            "uid": "XXX",
            "name": "Costco",
            "order_flag": 3,
            "is_default": false,
            "reminders_list": "Costco"
        },
        {
            "uid": "XXX",
            "name": "My Grocery List",
            "order_flag": 0,
            "is_default": true,
            "reminders_list": "Paprika"
        }
    ]
}

GET groceries. checked items are "purchased": true

var request = require('request');
var options = {
  'method': 'GET',
  'url': 'https://www.paprikaapp.com/api/v2/sync/groceries',
  'headers': {
    'Authorization': 'Bearer YOUR_TOKEN'
  }
};
request(options, function (error, response) {
  if (error) throw new Error(error);
  console.log(response.body);
});
{
    "result": [
        {
            "uid": "XXXX",
            "recipe_uid": null,
            "name": "popsicles",
            "order_flag": 316,
            "purchased": true,
            "aisle": "Miscellaneous",
            "ingredient": "popsicles",
            "recipe": null,
            "instruction": "",
            "quantity": "10",
            "separate": false,
            "aisle_uid": "XXXX",
            "list_uid": "XXXX"
        }
   ]
}

POST a new grocery list item. must be gzipped array of new grocery list items into the data form. can add as many as you like. specifying an existing uid will update it

const zlib = require('zlib');
const fs = require('fs');
var axios = require('axios');
var FormData = require('form-data');
const os = require('os');

var jsonData = [{
    uid: 'XXXX', // create a new guid for each new item
    name: 'popsicles',
    ingredient: 'popsicles',
    quantity: '10',
    recipe_uid: null,
    order_flag: 316,
    purchased: false,
    aisle: 'Miscellaneous',
    recipe: null,
    instruction: '',
    separate: false,
    aisle_uid: 'XXXX',
    list_uid: 'XXXX'
}];

var reqData = JSON.stringify(jsonData);
var buffer = zlib.gzipSync(Buffer.from(reqData.toString("utf-8")));
const tmpFile = os.tmpdir() + "/tmp.json.gz";
fs.writeFileSync(tmpFile, buffer);

var formData = new FormData();
formData.append('data', fs.createReadStream(tmpFile));
const headers = Object.assign({
    'Authorization': 'Bearer YOUR_TOKEN'
}, formData.getHeaders());

var config = {
  method: 'post',
  url: 'https://www.paprikaapp.com/api/v2/sync/groceries',
  headers: headers,
  data: formData
};

var result = false;
let response = await axios(config);
if (response.status == 200) {
  console.log(response.data); // { result: true } if the request is successful
  result = response.data.result !== undefined && response.data.result == true;
} else {
  console.error(response);
}

@FeralFlora
Copy link

I was hoping I could use the API to randomly populate the meal plan with one meal per day, one week at a time. Does anyone have an example of adding recipes to the meal plan using the API?

@kgmorales
Copy link

is it possible to save a new recipe using the v1 sync/recipe api?

I'm having a little trouble following how to save a new recipe through the POST.

@mattdsteele, thank you,

@mattdsteele
Copy link
Author

@kgmorales It's been a while since I've tried saving data, but yes, you should be able to save a new recipe. You'll have to generate a new UUID yourself. See https://gist.github.com/mattdsteele/7386ec363badfdeaad05a418b9a1f30a?permalink_comment_id=3916047#gistcomment-3916047 for a link to some example Python code.

@salvaom
Copy link

salvaom commented May 14, 2023

I'm still having trouble uploading a picture to a recipe, If I post to /api/v2/sync/photo/{a new uud} I get:
{'error': {'code': 0, 'message': 'Photo not found during photo upload.'}}

And if I post to /api/v2/sync/recipe/{recipe.uid} with the photo_upload argument I get
{'error': {'code': 0, 'message': 'Invalid photo filename.'}}

How can photos be uploaded to a recipe?

@jm-lamotte
Copy link

jm-lamotte commented Sep 18, 2023

I'm still having trouble uploading a picture to a recipe, If I post to /api/v2/sync/photo/{a new uud} I get: {'error': {'code': 0, 'message': 'Photo not found during photo upload.'}}

And if I post to /api/v2/sync/recipe/{recipe.uid} with the photo_upload argument I get {'error': {'code': 0, 'message': 'Invalid photo filename.'}}

How can photos be uploaded to a recipe?

Hello,
Here's the code I use for the upload (my photos are local, so I can't use requests to get the image content)

@dataclass
class RecipePhoto():
	uid: str = field(default_factory=lambda: str(uuid.uuid4()).upper())
	filename: str = ""
	name: str = ""
	order_flag: int = 1
	recipe_uid: str = ""
	hash: str = field(
		default_factory=lambda: hashlib.sha256(
			str(uuid.uuid4()).encode("utf-8")
		).hexdigest()
	)
	photo_url: Optional[str] = None
	deleted: bool = False

	def _request(self, method, path, token_r, authenticated=True, **kwargs):
		if authenticated:
			kwargs.setdefault("headers", {})[
				"Authorization"
			] = f"Bearer {token_r}"
		result = requests.request(method, path, **kwargs)
		result.raise_for_status()

		if "error" in result.json():
			raise RequestError()

		return result

	def as_gzip(self) -> bytes:
		return gzip.compress(self.as_json().encode("utf-8"))

	def as_json(self):
		return json.dumps(self.as_dict())

	def as_dict(self):
		return asdict(self)

	def calculate_hash(self) -> str:
		fields = self.as_dict()
		fields.pop("hash", None)

		return hashlib.sha256(
			json.dumps(fields, sort_keys=True).encode("utf-8")
		).hexdigest()

	def update_hash(self):
		self.hash = self.calculate_hash()

	def upload_photo(self, token):
		self.update_hash()
		files = {'data': self.as_gzip()}
		photo_data = self.get_photo_data()
		if self.photo_url :
			files['photo_upload'] = (self.filename, open(self.photo_url, 'rb'))
		self._request(
			"post",
			SYNC_PHOTO_URL(self.uid),
			token,
			files=files,
		)
		return self.uid

Then the call to the upload. RecipePhoto is a class with API fields for photos.

		Photo = RecipePhoto(
			filename = <the name of your photo file>,
			order_flag = 0, (Not sure how this is used)
			name = <the name of your photo>,
			recipe_uid = <the UID of the RECIPE you want to link the photo to>,
			photo_url = <LOCAL PATH TO THE PHOTO>,
			)

		Photo_UId = Photo.upload_photo(<your auth token>)

With this, I add the photo right after I created my recipe, and it shows when I display the photos of the recipe. It does not show in the recipe list, as the thumbnail is not set. I'm struggling on that part.

EDIT:
For the thumbnail, once you have uploaded your recipe and your photo, update the Recipe with photo_large (I use a description of the photo) and photo_url to the local url to your image

	Recipe.photo_large = <description>
	Recipe.photo_url = <path to the file>

Then update the recipe and attach the photo with a hah:

	def upload_recipe(self, token):

		files: Dict = {}

		if self.photo_url :
			self.photo = generate_uuid() + "." + self.photo_url.split(".")[-1]
			self.photo_hash = self.calculate_hash()
			files['photo_upload'] = (self.photo_url.split("/")[-1], open(self.photo_url, 'rb'))

		print(self)

		self.update_hash()

		files['data'] = self.as_gzip()


		self._request(
			"post",
			SYNC_RECIPE_URL(self.uid),
			token,
			files=files,
		)

		return self.uid


	Recipe_Definition["Recipe_Paprika_Uid"] = Recipe.upload_recipe(PAPRIKA_TOKEN)

@vostersc
Copy link

vostersc commented Feb 15, 2024

For anyone struggling w login, here is an example curl.

curl -X POST https://paprikaapp.com/api/v2/account/login -d 'email=ENTER_EMAIL&password=ENTER_PASSWORD

You'll get an auth token in response. You can use that for future requests. Not sure on TTL.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment