Member Website API¶
Warning
This integration automates unsupported HTML forms on
foods.fatsecret.com. It is separate from the supported FatSecret
Platform API and can break when the member website changes.
The member-web client supports account recipe CRUD, diary entries, and RDI operations that the official API does not expose for owned pending recipes. Credentials remain in process memory and should come from environment variables or an operating-system secret store.
Python client¶
from fatsecret import FatsecretWebClient, WebIngredientWrite, WebRecipeWrite
with FatsecretWebClient(
username,
password,
timeout=30,
retries=True,
) as client:
recipes = client.list_recipes()
recipe = client.get_recipe(recipes[0].recipe_id)
created = client.create_recipe(
WebRecipeWrite(
title="Bean Stew",
description="A simple bean stew.",
servings="4",
prep_minutes=10,
cook_minutes=45,
meal_types=["main_dishes"],
directions=["Combine ingredients.", "Cook until tender."],
)
)
client.add_recipe_ingredient(
created.recipe_id,
WebIngredientWrite(food_id=35755, amount="100"),
)
An omitted portion_id resolves the food-specific grams portion. Pass an
opaque nonzero portion_id returned by list_food_portions when an exact
non-grams serving is required. The client does not perform semantic food-name
searches. FatSecret uses -1 as the grams portion for some foods, so callers
must not assume portion IDs are positive.
Member diary¶
Owned recipes and ordinary foods use one member-diary interface. Omit
portion_id to choose grams when available or the sole serving for an owned
recipe:
from fatsecret import WebDiaryEntryWrite
entry = client.add_diary_entry(
WebDiaryEntryWrite(
item_id=136925003,
entry_name="spaghetti - 09/03/2026",
amount="259",
meal="dinner",
date=20699,
)
)
Use list_diary_item_portions when an item has multiple non-gram portions.
Owned recipe servings use portion ID 0 and grams can use -1; these are
valid website sentinels and are intentionally separate from official API
serving IDs. The website stores gram quantities as whole numbers, so the
client rejects fractional grams before writing instead of silently rounding.
Diary entries snapshot recipe nutrition. Updating a recipe does not update existing diary entries. To refresh nutrition, list affected entries, delete them, and create replacements after the recipe mutation has been verified. This is an explicit two-step workflow, not an atomic operation.
timeout and retries have the same meaning as on the official
Fatsecret client. Safe GET requests use the shared transient retry policy:
three attempts, with authoritative Retry-After delays and exponential
jitter when FatSecret does not provide a delay. Server-provided delays are not
capped by the 30-second network timeout. Pass retries=False to disable the
policy or a configured tenacity.Retrying instance to replace it. Login and
mutation POST requests are never retried automatically.
HTTP facade¶
Install the optional server dependencies:
pip install "fatsecret[facade]"
Configure one member account and a separate facade bearer token:
export FATSECRET_USERNAME=member-name
export FATSECRET_PASSWORD=member-password
export FATSECRET_FACADE_TOKEN=a-long-random-api-token
export FATSECRET_FACADE_DB=/var/lib/fatsecret/member-facade.sqlite3
fatsecret-member-api
FatSecret credentials are server-managed. Never include them in HTTP or MCP
request bodies. The facade binds to 127.0.0.1:8000 by default; place an
authenticated reverse proxy in front of it for remote access.
The manually maintained OpenAPI 3.1 contract is
docs/api-spec/member-web.openapi.yaml. Its server URL includes /v1.
The facade accepts these optional operational settings:
Variable |
Default |
Purpose |
|---|---|---|
|
|
Upstream request timeout in seconds. |
|
|
Enable shared retries for safe upstream GET requests. |
|
|
Wait and replay a mutation only after an explicit |
|
|
Delay used when FatSecret omits |
|
|
Minimum pacing between copied ingredients. |
|
|
Server bind address. |
|
|
Server bind port. |
Recipe copies¶
A copy is a durable asynchronous operation because FatSecret requires one write per ingredient and can rate-limit the workflow:
POST /v1/member/recipes/135651874/copies
Authorization: Bearer <facade token>
Idempotency-Key: <unique request key>
Content-Type: application/json
{"title": "baked beans - 08/26/2026"}
The response is 202 Accepted with an operation URI in Location. Read
that URI until status is completed. A waiting operation includes
retry_after and can be resumed with:
POST /v1/member/operations/{operation_id}/resume
Reuse the original idempotency key after a client timeout. A different payload
with the same key returns 409 Conflict.
Mutation safety¶
The official and member-web clients share retry configuration for safe reads.
Neither client retries mutation requests automatically. Every member-web
mutation is read back and verified. An ambiguous write returns a problem
response with upstream_outcome set to unknown; reconcile the resource
before issuing another mutation.
The facade serializes writes for its configured account and persists copy checkpoints and idempotency responses in SQLite. Rendered ingredient text is not used for copy verification because FatSecret reformats some quantities; verification compares food IDs, portion IDs, and decimal amounts.