🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@recode-software/cookidoo-api

Package Overview
Dependencies
Maintainers
1
Versions
2
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@recode-software/cookidoo-api

TypeScript client for the Cookidoo custom-recipes API (Thermomix). Auth, CRUD, typed MODE/INGREDIENT annotation builders, and a Polish step-text parser.

latest
Source
npmnpm
Version
0.1.1
Version published
Maintainers
1
Created
Source

@recode-software/cookidoo-api

Unofficial TypeScript client for the Cookidoo custom recipes API ("Utworzone przepisy" / "Created recipes"). Build Thermomix-actionable recipes programmatically: copy a public recipe into your account, then PATCH it with structured MODE buttons and INGREDIENT chips that render natively on TM7 / TM6.

This is a ground-up rewrite in TypeScript — not a port of miaucl/cookidoo-api. It fixes the recipeUrl bug in that library's add_custom_recipe_from (which points at /recipes/recipe/{lang}/{id} and 404s on community-authored recipes) by always using the documented cookidoo.{cc}/created-recipes/public/recipes/{lang}/{id} form.

Status: unofficial, reverse-engineered. Vorwerk can change the API at any time. Use for your own account — don't hammer the service.

Install

npm i @recode-software/cookidoo-api

Requires Node 20+ (uses global fetch).

Quick start

import { CookidooClient, parseStep, mode, ingredient, step } from "@recode-software/cookidoo-api";

const client = new CookidooClient({
  email: process.env.COOKIDOO_EMAIL!,
  password: process.env.COOKIDOO_PASSWORD!,
  country: "pl",    // tmmobile subdomain
  language: "pl",   // URL path segment
});

// 1. Copy a public recipe into your account
const recipe = await client.recipes.copyFromPublic({
  publicId: "01KB04WSJP4SHNBKJK4H4FT0PZ",
  servingSize: 1,
});

// 2. PATCH meta (name, ingredients, yield, times)
await client.recipes.patchMeta(recipe.recipeId, {
  name: "T1 D1 Śniadanie · Gofry cynamonowe",
  ingredients: [
    { type: "INGREDIENT", text: "50 g jabłka" },
    { type: "INGREDIENT", text: "5 g masła ekstra" },
  ],
  yield: { value: 1, unitText: "portion" },
  prepTime: 1800,
  totalTime: 1800,
});

// 3. PATCH instructions with MODE + INGREDIENT annotations.
//    `parseStep` autodetects "praż", "Varoma", "obr. X", "wsteczne" …
const stepA = parseStep(
  "Do naczynia włóż jabłko, praż 10 min/100°C/obr. 1. Przełóż.",
  [{ display: "50 g jabłka", stem: "jabłk", amount: 50, unit: "g" }],
);

// Build a step manually if the parser doesn't recognize your phrasing
const stepB = step("Ubij 3 min/obr. 3,5.", [
  mode.manual({ offset: 5, length: 14, time: 180, speed: "3.5" }),
]);

await client.recipes.patchInstructions(recipe.recipeId, [stepA, stepB]);

API

new CookidooClient(options)

OptionTypeDefault
emailstring
passwordstring
countrystring
languagestringcountry
baseUrlstringhttps://{country}.tmmobile.vorwerk-digital.com
fetchtypeof fetchglobal fetch (lets you inject for tests)

The client performs OAuth2 password grant lazily on first API call, caches access_token + refresh_token, and refreshes once on a 401 response.

client.recipes

MethodDescription
list()List your custom recipes
get(id)Fetch a recipe (full view, with annotations)
copyFromPublic({publicId, servingSize?, retry?})Copy a public recipe into your account
patchMeta(id, meta)Update name / ingredients / yield / times
patchInstructions(id, steps)Replace instructions[] with new Step[]
delete(id)Delete a custom recipe

Always do two PATCH calls (meta + instructions) — sending both in one request triggers validationError. This matches what the web UI does.

Builders (@recode-software/cookidoo-api/builders)

Type-safe constructors for MODE / INGREDIENT annotations. Each takes the {offset, length} of the substring in the step text to pin the chip/button.

import { mode, ingredient, step } from "@recode-software/cookidoo-api/builders";

mode.manual({ offset, length, time: 20, speed: "5" });            // ⚠ unsupported on custom recipes
mode.browning({ offset, length, time: 600, temperature: 140 });   // 140|145|150|155|160
mode.blend({ offset, length, time: 30, speed: "7" });             // 6|6.5|7|7.5|8
mode.steaming({ offset, length, time: 900, speed: "1" });         // accessory: Varoma (no temperature field)
mode.warmUp({ offset, length, temperature: 70, speed: "1" });     // soft|1|2
mode.turbo({ offset, length, time: 2, pulseCount: 1 });
mode.dough({ offset, length, time: 120 });
mode.riceCooker({ offset, length });

ingredient.simple({ offset, length, description: "50 g jabłka" });
ingredient.structured({
  offset, length,
  description: "50 g jabłka",
  amount: 50, unit: "g",
});

Parser (@recode-software/cookidoo-api/parser)

Polish step-text parser (the only verified grammar). Detects:

  • N min/M°C/obr. XMODE manual with temperature
  • N min/Varoma/obr. XMODE steaming
  • praż N min/M°C/obr. XMODE browning (temperature clamped to 140–160 °C)
  • N min/obr. XMODE manual (no temperature)
  • wsteczne suffix → direction: "CCW"
  • ingredient stems in the step → INGREDIENT with nested VOLUME annotation
import { parseStep } from "@recode-software/cookidoo-api/parser";

const s = parseStep("praż 10 min/100°C/obr. 1 jabłko", [
  { display: "50 g jabłka", stem: "jabłk", amount: 50, unit: "g" },
]);

Time conversion: s/sek → 1, min → 60, h → 3600. Ranges (4–6 minut) collapse to the first number.

stem is a substring of the ingredient word that matches any inflected form in the step text. Polish nouns decline ("jabłko", "jabłka", "jabłkiem"), so the stem "jabłk" catches them all via \bjabłk\w*.

Other locales

The parser is grammar-driven. PL (grammars.pl) is the only built-in and the only grammar verified against live Cookidoo payloads. To parse step text from another market (e.g. DE), supply a custom Grammar:

import { parseStep, type Grammar } from "@recode-software/cookidoo-api";

const grammarDe: Grammar = {
  name: "de",
  timeUnits: { s: 1, sek: 1, min: 60, h: 3600, std: 3600 },
  timeUnitsPattern: "min|sek\\.?|std\\.?|s|h",
  speedLabel: "Stufe",
  reverseWord: "linkslauf|rückwärts",
  browningTrigger: "anbr(a|ä)t",
};

parseStep("Rühren 20 s/60°C/Stufe 2.", [], { grammar: grammarDe });

If you verify a grammar against a real account in your market, PRs welcome.

Rate limits

Only POST /created-recipes/{lang} (copy-from-public) is throttled. After ~6–8 successful POSTs in a minute the endpoint returns 429 Too Many Requests with code: "importFailed". There's no Retry-After header.

The library exposes CookidooRateLimitError and copyFromPublic has built-in backoff enabled by default (30 → 60 → 90 → 120 seconds):

// Default backoff
await client.recipes.copyFromPublic({ publicId });

// Custom delays + progress callback
await client.recipes.copyFromPublic({
  publicId,
  retry: {
    delaysMs: [30_000, 60_000, 90_000],
    onRateLimit: ({ attempt, delayMs }) =>
      console.log(`rate limited, waiting ${delayMs}ms (attempt ${attempt})`),
  },
});

// Opt out
await client.recipes.copyFromPublic({ publicId, retry: false });

PATCH /created-recipes/{lang}/{id} is not rate-limited — you can hammer it.

Known quirks

  • name: "manual" is silently unsupported on custom recipes. The API saves it, but the Cookidoo web/TM7 UI renders the chip struck-through with unsupported=true. parseStep / findModeAnnotations therefore skip manual-producing patterns by default (use { emitManual: true } only if you're generating data for a non-custom-recipe context). For step text like 20 s/obr. 5 that has no specific Tryb, leave it as plain text — users will type the values on the machine.
  • steaming has NO temperature field. Only time, speed, direction, and accessory. Sending temperature returns 400 validationError. The builder and parser both reflect this.
  • Custom Lists and custom recipes don't mix. POST /organize/{lang}/api/custom-list/{id} returns a fake 200 OK when you try to add a custom recipe — the list stays empty. Use name prefixing (T1 D1 Śniadanie · …) or attach to the calendar via POST /planning/{lang}/api/my-day instead.
  • tools is read-only. The server populates it based on which TM generations support your step modes.
  • totalTime/prepTime are seconds on PATCH. The Schema.org GET view may return them as ISO-8601 durations ("PT30M").

Errors

import {
  CookidooError,
  CookidooAuthError,
  CookidooRateLimitError,
} from "@recode-software/cookidoo-api";

try {
  await client.recipes.copyFromPublic({ publicId });
} catch (err) {
  if (err instanceof CookidooRateLimitError) {
    // 429 — copy endpoint throttled
  } else if (err instanceof CookidooAuthError) {
    // 401 on token endpoint
  } else if (err instanceof CookidooError) {
    console.error(err.status, err.body);
  }
}

Development

npm install
npm run typecheck
npm test                  # unit tests, no network
npm run test:integration  # real API — requires .env (see .env.example)
npm run build

Integration tests read credentials from .env (or ../.env). They create recipes with a __test__ prefix and delete them in afterEach; an afterAll sweep removes any leftovers. Required variables:

COOKIDOO_EMAIL=
COOKIDOO_PASSWORD=
COOKIDOO_COUNTRY=pl
COOKIDOO_LANGUAGE=pl
COOKIDOO_TEST_PUBLIC_RECIPE_ID=01KB04WSJP4SHNBKJK4H4FT0PZ   # optional

Credits

Thanks to miaucl/cookidoo-api (Python) for paving the way.

License

MIT

Keywords

cookidoo

FAQs

Package last updated on 20 Apr 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts