Output:
pages 78

---PAGE 3 ---
TABLE OF CONTENTS
The Min-Max Program Explained
Who This Program Is For
How This Program Works
Measuring Effort with Reps
in Reserve (RIR)
Brief Program Structure Overview
How to Run This Program
Muscle Anatomy
The Min-Max Program
Frequently Asked Questions
Final Thoughts
1
3
5
8
10
12
16
20
21
25

---PAGE 9 ---
For Lifters 1 and 2: Short, high-intensity workouts

I designed the program to maximize hypertrophy for lifters who only have about 45 minutes to train, 4 to 5
times per week. That’s simply not enough time to do 3 to 4 sets of every exercise. Instead, you’ll do just 1 or 2
hard sets of most exercises, usually taking them to failure.

This setup gives you the opportunity to focus more on quality over quantity. In my coaching experience, low-
volume, high-intensity training leads to better focus and execution on each individual set. When you know you
only have 1 or 2 sets to get the job done, there’s no room to coast. You have to make every set count.

It also helps reduce junk volume—excessive sets that have severely diminishing returns or sets that aren’t
especially challenging or stimulating. By keeping volume lower and effort higher, you maximize efficiency and
avoid wasting time or energy.

For Lifter 3: Maximizing muscle retention

The reality is, unless you’re a beginner, or your training has been suboptimal and inconsistent, gaining muscle
in a caloric deficit is unlikely. Maintaining the muscle you’ve built is a more realistic goal for most lifters.

To be sure, some of you will probably gain a little muscle on a cut. But even if you don’t, you’ll still appear far
more jacked after you cut some of the fat sitting on top of the muscle you already have.

That shift in expectation calls for a shift in programming. If the goal in a surplus is to gain as much muscle as
possible, then the goal in a deficit should be to lose as little muscle as possible. Different goals require
different strategies.

That’s what I set out to create with Min-Max: the most effective training plan for cutting. I’ve had tremendous
success running it. I’ve maintained muscle fullness, even while getting to single-digit body fat. As a bonus, I’ve
actually gained strength, and my recovery has been better than ever. Most importantly, I’ve found it far more
sustainable and enjoyable than my usual high-volume approach.

As I said, this program will work with other goals. But for the specific goal of fat loss, you need a caloric deficit
—burning more calories than you consume. And in a caloric deficit, your body has fewer resources to support
muscle repair, glycogen replenishment, and anabolic signaling.

The Min-Max Program is designed to support effective, sustainable training with fewer calories than your body
is used to having. Nearly every aspect of the program re

---PAGE 11 ---
Measuring Effort
with Reps
in Reserve (RIR)
8
The Min-Max Program

---PAGE 12 ---
9
The Min-Max Program
Because effort is crucial on this program, it must be monitored intentionally. This program uses a method
called reps in reserve (RIR) to help you gauge how close you are to failure on each set.

It’s an estimate of how many more reps you could have done if you had kept going. For example:

For squat, barbell incline press, leg press, and Smith machine lunge:
RIR 0 means you’re confident you had zero reps left, but for safety, you didn’t attempt another rep. The risk of
injury on those specific lifts exceeds the potential rewards.

For all other exercises:
RIR 0 means you did attempt the final rep and failed to complete it.
Each exercise includes a column that tells you the target RIR for each set. For example, in Week 2, the pull-up
calls for RIR 1 on the first set and RIR 0 on the second. That means you should stop one rep shy of failure on
your first set, and go all the way to failure on your second.

Making it work requires a mindset shift. When you’re no longer doing 3 or 4 sets per exercise, you have to
make each set count. Stopping a set short of the RIR target is a missed opportunity.

That said, it might take some practice to get good at judging RIR. At first you might stop a set short of the RIR
target simply because you’re used to training that way. Other times you’ll hit failure when you thought you still
had a couple reps left in the tank. It’s totally normal. If you’re honest with yourself and push hard, you’ll get
better at gauging your effort, and your results will reflect that newly acquired skill.

Just keep in mind, since volume is low, most sets will go all the way to 0RIR. Go into each workout ready to
push!

Clarification for RIR 0:

RIR 2 = you stopped the set with 2 reps left in the tank
RIR 1 = you had 1 rep left
RIR 0 = you couldn’t have done another rep because you reached failure

---PAGE 14 ---
11
The Min-Max Program
Training blocks
Training split
Workout duration
Volume
Effort
Exercise substitutions
Equipment
The 12-week program is split into two 6-week blocks.

You’ll take most working sets (another way to describe hard sets) to RIR 0 or 1.
Each exercise includes two suggested options. But those are by no means your
only choices. Feel free to swap in any movement that works the same muscles
in the same movement pattern.
I based the program on equipment you’ll find in most commercial gyms. But if
your gym doesn’t have a specific machine or apparatus, or you train at home
with limited options, just make the appropriate substitution.
There are 4 workouts per week: Full Body / Upper / Lower / Arms + Delts
Each session should take about 45 minutes, although it will vary with warm-up
time and rest between sets.
You’ll do 4 to 9 sets per week for most muscle groups.
You’ll do 2-3 sets for abs and calves
Both blocks begin with a lighter week, in which you’ll do most sets to RIR 1 or
2, rather than RIR 0 or 1.
Block 2 introduces intensity techniques on the last set.

---PAGE 16 ---
13
The Min-Max Program
Warming up

Within the program, I give a brief overview of the warm-up protocol for each workout. The main thing is literally
getting your body warmed up by increasing your core temperature with some light cardio. From there, you can
ease into your working sets by doing 1-2 (sometimes 3) warm up sets that gradually get a bit heavier. No need
to overthink your warm up. Get warm and get to work.

Just in case though, here is a full breakdown for those who’d like more specific guidance on warming up:
5-10 minutes
1 Warm-Up Set Listed
3 Warm-Up Sets Listed
4 Warm-Up Sets Listed
2 Warm-Up Sets Listed
10 reps per side
15 reps per side
10 reps per side
10 reps per side
10 reps per side
Light cardio on your choice of machine (treadmill, stair climber, elliptical, bike, etc.)
Use ~60% of your planned working weight for ~6-10 reps
(or until you feel loose and ready to lift)
Warm-up set #1: ~45% of planned working weight for ~6-10 reps
Warm-up set #2: ~65% of planned working weight for 4-6 reps
Warm-up set #3: ~85% of planned working weight for 3-4 reps
Warm-up set #1: ~45% of planned working weight for ~6-10 reps
Warm-up set #2: ~60% of planned working weight for 4-6 reps
Warm-up set #3: ~75% of planned working weight for 3-5 reps
Warm-up set #4: ~85% of planned working weight for 2-4 reps
Warm-up set #1: ~50% of planned working weight for ~6-10 reps
Warm-up set #2: ~70% of planned working weight for 4-6 reps
Arm circles (optional)
Cable external rotations (optional)
Arm swings (optional)
Side-to-side leg swings (optional)
Front-to-back leg swings (optional)
Perform the following general warm-up before every workout. It can be as simple as 5 minutes of
light cardio, or can include some or all of the exercises listed below.
The program indicates the number of warm-up sets you should perform.
Exercise-Specific Warm-Up
General Warm-Up

---PAGE 17 ---
14
The Min-Max Program
Workout Breakdown
Exercise
Last-set intensity technique
Each exercise includes a clickable demo link for proper technique.
The suggested number of light sets to perform before working sets.
Refer to the exercise-specific warm-up above for full details.
These are your hard sets.
Use the prescribed RIR target to determine effort level.
These are only used after the final working set of an exercise.
1.
2.
3.
4.
Warm-up sets
Working sets
Block 1 includes no intensity techniques.
Block 2 introduces:
Drop sets: After reaching failure, reduce the weight by ~25% and go to failure again. Repeat once
more (for a total of 3 back-to-back sets).
Lengthened partials: After reaching failure with a full range of motion (ROM), continue to perform
reps with a partial range of motion (in the stretched position) until failure.
Myo-reps: After failure, rest 5 seconds and attempt 2 more reps. Repeat this cycle (5 seconds
rest, followed by 2 reps) until you fail to complete 2 full-ROM reps. (This usually happens by the
third cycle, if not before.)
Weighted static hold: After failing on the machine chest press (or dumbbell bench press
substitution), hold the bottom (stretched) position under full tension for 30 seconds.
1
2
3
4
5
6
7
8
9
10

---PAGE 18 ---
15
The Min-Max Program
Rep range
Tracking load and reps
Once you can perform all working sets at the top of the rep range, increase the load next session.
If increasing the weight would cause you to drop below the range, keep the same weight and extend
the rep range slightly. For example: If the range is 6 to 8, and adding weight drops you to 4 to 5 reps,
stick with the previous load and aim for 6 to 10.
RIR 1 = 1 rep left in the tank
RIR 2 = 2 reps left in the tank
For the squat, barbell incline press, leg press, and Smith machine lunge, RIR 0 means you had zero
reps left, but you didn’t attempt and fail the final rep.
For all other exercises, RIR 0 means you attempted and failed the final rep.
This column tells you how close you should be to failure on working sets, using RIR:
Clarification for RIR 0:
Between working sets: follow the listed rest time (see Frequently Asked Questions for a more detailed
explanation).
Between warm-up sets: rest 0 to 60 seconds.
Between exercises: rest 1 to 5 minutes, depending on how demanding the exercise is.
Heavy compound exercises generally require 3-5 minutes of rest between sets for recovery. Lighter
compounds or isolation exercises usually only need 1-2 minutes of rest between sets.
Each exercise includes two alternative movements.
Feel free to swap in either one (or one I didn’t list) if the featured exercise doesn’t work for you.
Each exercise includes coaching cues in the Notes section.
Be sure to read these before warm-ups so you can apply any new cues while the weight is still light.
Log the weight you used and reps you completed for your working sets (if applicable).
5.
6.
7.
8.
9.
10.
Failure?
Rest
Substitution options
Notes

---PAGE 23 ---
the min-max
program
20
The Min-Max Program

---PAGE 24 ---
The Min-Max Program
i
Due to the lower volume in this program, it's essential that you approach each set with focused intent and high effort.

Nearly every set in this program should be taken to failure, or 1 rep shy of failure. The only exceptions are in Week 1 (an intro week) and Week 7 (a deload week), where you won't train quite as hard.

In the program, I use Reps In Reserve (RIR) to indicate which sets you should take to failure. An RIR of 0 means you hit failure (zero reps in the tank). An RIR of 1 means you stop 1 rep shy of failure.

For heavy compound lifts like the Squat, Barbell Incline Press, Leg Press, and Smith Machine Lunge, an RIR of 0 means you couldn’t complete another rep, but for safety you don't need to actually attempt and fail the final rep unless you want to.

For all other exercises, an RIR of 0 means that you should actually go for another rep, even if you think you can't get it. On these exercises, go until you physically can't complete the rep and literally fail.
Start each workout with about 5 minutes of light cardio to raise your core body temperature.

Each exercise includes the recommended number of exercise-specific warm-up sets, which should be light and not fatiguing.

For a more detailed breakdown of warm-up protocols, refer to page 13.
IMPORTANT PROGRAM NOTES
WARM-UP PROTOCOL
PLEASE READ BEFORE STARTING!

---PAGE 25 ---
WEEK 1
Nordic Ham Curl
See Notes
DB Incline Press
Machine Lateral
Raise
1-Arm Cable
Pulldown
Donkey Calf Raise
Seated Leg Curl
See Notes
Smith Machine
Incline Press
Cable Y-Raise
Lat Pulldown
(Wide Grip)
Leg Press Calf
Press
1-2 min
3-5 min
3-5 min
1-2 min
2-3 min
1-2 min
1
3
2
0
2
0
0
2
1
N/A
1
N/A
6-8
6-8
6-8
8-10
6-8
6-8
2
2
2
1
2
1
1-2
2-4
2-4
0-1
1-2
0-1
Lying Leg Curl
Squat (Your
Choice)
Barbell Incline
Press
Incline DB Y-Raise
Pull-Up (Wide
Grip)
Standing Calf
Raise
N/A
N/A
N/A
N/A
N/A
N/A
Set the machine so that you get the biggest stretch possible at the bottom.
Prevent your butt from popping up as you curl.
This can be a Barbell Back Squat, Barbell Front Squat, Pendulum Squat,
Hack Squat, Belt Squat, or Smith Machine Squat.
A 30° or 45° bench will work here. Pause for 1 second at the bottom of
each rep while maintaining tension on the pecs.
Use a 30° incline bench (back against the bench) and lift the weight up and
out in a Y shape.
Control the negative and feel your lats pulling apart. Full ROM!
1-2 second pause at the bottom of each rep. Instead of just going up onto
your toes, think about rolling your ankle back and forth on the balls of your
feet.
Exercise
Last-Set Intensity
Technique
Substitution
Option 1
Substitution
Option 2
NOTES
WORKING
SETS
Warm-up
Sets
Rep
Range
RIR
(Set 1)
RIR
(Set 2)
Rest
Full Body
BLOCK 1
INTRO WEEK
1-2 Rest Days
The Min-Max Program
1
Tracking Load and Reps
Failure?
SET 1
lOAD
REPS
SET 2
LOAD
REPS


memory

User plans to restart gym using Jeff Nippard's Min-Max 4-day program; friend Andrés will train with him and is returning after ~20 years away from the gym.


Memory at 1,363/1,375 chars. Adding this entry (155 chars) would exceed the limit. Replace or remove existing entries first.

_Learn about the person you're helping. Update this as you go._

**Name:** Ignacio (Igna, also goes by Chicho)

**Timezone:** UTC-3 (Buenos Aires, Argentina)

Prefiere respuestas en texto por defecto (para ahorrar tokens de ElevenLabs)

Audio solo cuando él lo pida explícitamente

Prefiere respuestas directas; si pregunta por Discord/plataformas, quiere límites claros sin que le cambien el flujo.

Cuando usa audio: prefiere acento argentino y TTS de calidad (ElevenLabs/OpenAI).

Tiene Tailscale en su computadora personal — se puede usar para acceso cross-device

Gmail: ignaciolagosruiz@gmail.com (gog configurado, read-only)

No quiere que Pipo interactúe con otras personas vía WhatsApp/mensajería (por ahora)

Vive en Argentina — scraping de sitios locales puede necesitar exit node argentino (Pixel 6a via Tailscale)

APIs config: DeepSeek (key en Bitwarden, en Hermes), Kimi K2.5, Mistral ($10), OpenRouter ($0.53 backup), ClawRouter wallet 0x1d1776D... ($11.70 Base). DeepSeek/Kimi NO como daily driver — solo workers/subagentes.

Dog name is Chivi (with v), not Chibi. Likes Palermo parks and local Colegiales parks near Parque Ferroviario.


todo

Extraer plan/spreadsheet de 4 días y ejercicios de semana 1

Proponer protocolo seguro para estimar rep max en 1–2 días y logging en Hevy


Acceder y listar archivos del Drive de Jeff Nippard MinMax

Revisar datos actuales de Hevy de Ignacio


Sí, les doy una mano. Revisé el Drive, bajé el spreadsheet **Min-Max_Program_4x.xlsx** y el PDF de 4 días. También verifiqué Hevy: tenés **32 workouts** cargados; tu último fue **Mar 16, 2026 — Full Body**, así que para vos también cuenta como “vuelta”, no como continuidad plena.

Mi recomendación clara: **no hagan un día entero de testeo de 1RM real**. No hace falta y sería mala idea, especialmente para Andrés después de 20 años. Para este programa conviene calcular **e1RM / training max aproximado** usando sets submáximos dentro del rango del programa, no máximos reales.

## Resumen del programa 4 días

La semana 1 es **Intro Week**, más liviana. Split:

1. **Full Body**
2. descanso 1–2 días
3. **Upper**
4. **Lower**
5. **Arms/Delts**
6. descanso 1–2 días

Semana 1 ya trae RIR más conservador en los compuestos:

### Día 1 — Full Body, Semana 1

| Ejercicio | Sets | Reps | RIR objetivo |
|---|---:|---:|---|
| Lying Leg Curl | 2 | 6–8 | 1, 0 |
| Squat — elección | 2 | 6–8 | 3, 2 |
| Barbell Incline Press | 2 | 6–8 | 2, 1 |
| Incline DB Y-Raise | 1 | 8–10 | 0 |
| Pull-Up Wide Grip | 2 | 6–8 | 2, 1 |
| Standing Calf Raise | 1 | 6–8 | 0 |

### Día 2 — Upper, Semana 1

| Ejercicio | Sets | Reps | RIR objetivo |
|---|---:|---:|---|
| Close-Grip Lat Pulldown | 2 | 8–10 | 2, 1 |
| Chest-Supported T-Bar Row | 2 | 8–10 | 2, 1 |
| Machine Shrug | 1 | 6–8 | 1 |
| Machine Chest Press | 2 | 8–10 | 2, 1 |
| High-Cable Lateral Raise | 2 | 8–10 | 1, 0 |
| 1-Arm Reverse Pec Deck | 1 | 8–10 | 0 |
| Cable Crunch | 2 | 6–8 | 1, 0 |

### Día 3 — Lower, Semana 1

| Ejercicio | Sets | Reps | RIR objetivo |
|---|---:|---:|---|
| Leg Extension | 2 | 8–10 | 1, 0 |
| Barbell RDL | 2 | 6–8 | 3, 2 |
| Machine Hip Thrust | 2 | 6–8 | 2, 1 |
| Leg Press | 1 | 6–8 | 1 |
| Standing Calf Raise | 2 | 8–10 | 1, 0 |

### Día 4 — Arms/Delts, Semana 1

| Ejercicio | Sets | Reps | RIR objetivo |
|---|---:|---:|---|
| Bayesian Cable Curl | 2 | 6–8 | 1, 0 |
| Overhead Cable Triceps Extension | 2 | 8–10 | 1, 0 |
| Modified Zottman Curl | 1 | 8–10 | 0 |
| Cable Triceps Kickback | 2 | 8–10 | 1, 0 |
| DB Wrist Curl | 2 | 8–10 | 1, 0 |
| DB Wrist Extension | 2 | 8–10 | 1, 0 |
| Alternating DB Curl | 1 | 6–8 | 0 |
| Machine Lateral Raise | 2 | 8–10 | 1, 0 |
| Dead Hang opcional | 2 | tiempo | 0, 0 |

---

# Qué haría mañana

## Opción recomendada: NO “test day”, sino **hacer Día 1 y usarlo para estimar cargas**

Mañana hagan directamente **Full Body Semana 1**, pero con mentalidad de calibración.

Objetivo: salir con pesos iniciales seguros para:

- Lying Leg Curl
- Squat variant
- Incline Press
- Y-Raise
- Pull-Up / Lat Pulldown
- Standing Calf Raise

Y con eso ya pueden arrancar el programa.

Para cada ejercicio:

1. 5–10 min cardio suave.
2. Hacer warm-ups progresivos.
3. Elegir un peso que permita caer dentro del rango.
4. Registrar en Hevy:
   - peso
   - reps
   - RIR real percibido
   - nota si fue demasiado fácil/difícil
5. Ajustar para el segundo set si hace falta.

No necesitan calcular todos los rep max del programa antes de empezar. El programa está diseñado para que el tracking sea por **load + reps + RIR**, y se progresa sesión a sesión.

---

# Cómo estimar el rep max sin matarse

Para ustedes, especialmente Andrés, usaría esto:

## Para compuestos pesados

- Squat variant
- Incline Press
- RDL
- Leg Press
- Machine Chest Press
- Hip Thrust

No ir a fallo real. Usar un set de **6–8 reps con 1–3 RIR**.

Fórmula simple:

```text
e1RM ≈ peso × (1 + reps totales posibles / 30)
```

Donde:

```text
reps totales posibles = reps hechas + RIR
```

Ejemplo conceptual:

```text
80 kg × 6 reps con RIR 2
=> reps posibles = 8
=> e1RM ≈ 80 × (1 + 8/30)
```

Eso les da una estimación. Pero para el programa, más importante que el e1RM es saber:

> “Con qué peso puedo hacer 6–8 reps respetando el RIR objetivo”.

## Para aislados

- curls
- laterales
- leg curl
- leg extension
- calves
- pec deck
- crunches

No vale la pena calcular 1RM formal. Es medio ficticio y poco útil. Ahí simplemente busquen el peso correcto para el rango:

- si pide 8–10 con RIR 1, elegís un peso donde podrías hacer 11 reps máximo aprox.
- si pide 6–8 con RIR 0, elegís un peso donde fallás o casi fallás en 6–8.

---

# Para Andrés

Acá sería bastante conservador.

Si hace 20 años no va al gym, mañana no debería estar “calculando máximos”. Debería estar calculando **cargas de entrada**.

Para Andrés:

- Nada de 1RM real.
- Nada de probar ego weight.
- Nada de llegar a fallo en squat/RDL/leg press/incline press.
- En aislados puede acercarse más al fallo, pero igual la primera semana yo frenaría un poco antes si la técnica se rompe.
- Su prioridad mañana es:
  1. aprender máquinas
  2. técnica
  3. rango de movimiento
  4. tolerancia articular
  5. salir pudiendo caminar al día siguiente

Para él, incluso si el spreadsheet dice RIR 0 en aislados, yo haría la primera sesión con **RIR 1–2 real** y lo anotaría. La semana 2 se ajusta.

---

# Plan concreto para mañana

## Mañana: Día 1 — Full Body calibrado

### 1. Lying Leg Curl

- Warm-up: 1–2 sets livianos.
- Set 1: buscar 6–8 reps con RIR 1.
- Set 2: mismo peso o leve ajuste, buscar 6–8 cerca de RIR 0.
- Para Andrés: dejar RIR 1 aunque el programa diga 0.

### 2. Squat — elección

Mi recomendación:

- Vos: **Hack Squat / Smith Squat / Pendulum / Belt Squat** si el gym tiene. Barbell back squat solo si ya estás cómodo.
- Andrés: **Hack Squat, Smith Squat o Leg Press**, no barbell back squat el primer día.

Semana 1 pide RIR 3 y RIR 2. Perfecto para calibrar.

- Set 1: 6–8 reps con RIR 3.
- Set 2: 6–8 reps con RIR 2.

Este es ideal para estimar un e1RM seguro.

### 3. Barbell Incline Press

Yo consideraría cambiarlo por:

- Smith Machine Incline Press, o
- DB Incline Press

Especialmente para Andrés.

Mismo criterio:

- Set 1: 6–8 reps con RIR 2.
- Set 2: 6–8 reps con RIR 1.

### 4. Incline DB Y-Raise

Peso ridículamente bajo. En serio.

- Mancuernas de 1–4 kg pueden ser suficientes.
- Técnica > carga.
- 1 set de 8–10.

### 5. Pull-Up Wide Grip

Para ustedes mañana probablemente conviene:

- Lat Pulldown wide grip, o
- Assisted pull-up

Mejor que pelearse con dominadas si no están frescos.

- Set 1: 6–8 con RIR 2.
- Set 2: 6–8 con RIR 1.

### 6. Standing Calf Raise

- 1 set de 6–8.
- Pausa abajo 1–2 segundos.
- Acá sí pueden acercarse al fallo, pero sin rebotes.

---

# ¿Se puede calcular todo mañana?

## Respuesta corta

**No conviene.**

Poder, podrían testear muchos ejercicios, pero sería una sesión larguísima, fatigante y poco confiable. Después de meses sin ir, los pesos que salgan mañana van a estar contaminados por:

- falta de práctica técnica
- baja tolerancia al esfuerzo
- DOMS
- coordinación oxidada
- miedo/incomodidad en máquinas
- fatiga acumulada si prueban demasiadas cosas

## Mejor enfoque

### Día 1 — mañana

Calibran los ejercicios de **Full Body**.

### Día 2 — pasado mañana o luego de 1 día de descanso

Si están bien, hacen **Upper** y calibran:

- pulldown
- row
- shrug
- chest press
- lateral raise
- reverse pec deck
- crunch

### Día 3

Calibran **Lower**.

### Día 4

Calibran Arms/Delts.

O sea: el programa mismo les da la calibración. No hace falta un pre-test separado.

---

# Si igual quieren resolver lo máximo posible en 2 días

Haría esto:

## Día A — mañana

Full Body completo, más 1–2 pruebas suaves extra si tienen tiempo:

- Full Body del programa.
- Al final, solo si están bien:
  - Machine Chest Press: 1 set de prueba 8–10 RIR 2.
  - Leg Extension: 1 set de prueba 8–10 RIR 2.

## Día B — pasado o con 1 día de descanso

Upper + Lower “calibration lite”, no full workout pesado:

- Close-Grip Lat Pulldown
- Chest-Supported Row
- Machine Chest Press
- Leg Extension
- RDL liviano
- Leg Press liviano

Pero mi preferencia sigue siendo: **no compactarlo tanto**. Si van a empezar de vuelta, el mejor favor que se pueden hacer es no destruirse la primera semana.

---

# Regla de selección de peso

Para cada ejercicio, hagan ramp-up así:

## Si el ejercicio pide 2–4 warm-up sets

Ejemplo de protocolo:

1. barra/máquina muy liviana × 8–10
2. peso moderado × 5–6
3. peso casi de trabajo × 3–4
4. set de trabajo

Descansos de warm-up: 30–60 s.
Descansos de trabajo: lo que dice el programa.

## Cómo saber si el peso está bien

Para rango 6–8:

- Si hacen 8 reps y sienten que quedaban 4+ reps: demasiado liviano.
- Si hacen 6 reps y sienten que quedaba 0 cuando pedía RIR 2–3: demasiado pesado.
- Si hacen 7 reps y sienten que quedaban 2: perfecto para RIR 2.

Para rango 8–10:

- Si hacen 10 y quedaban 3+: subir la próxima.
- Si hacen 8 y casi mueren cuando pedía RIR 1–2: bajar.
- Si hacen 9 y quedaba 1: perfecto.

---

# Qué loguear en Hevy

Mañana loguearía todo como workout normal, no como “test”.

Nombre sugerido:

```text
Min-Max W1D1 — Full Body Calibration
```

Notas generales:

```text
Vuelta al gym. Calibración de cargas para Jeff Nippard Min-Max 4x. RIR anotado manualmente.
```

En cada ejercicio, en notas:

```text
Objetivo: 6-8 reps, RIR 2 aprox. Técnica OK / ajustar peso.
```

Y después me pasan:

- ejercicio
- peso
- reps
- RIR percibido
- si hubo dolor/molestia
- si fue máquina/barra/mancuerna
- peso corporal aproximado, especialmente para dominadas asistidas

Con eso yo les calculo:

- e1RM aproximado donde tenga sentido
- carga sugerida para la próxima vez
- si deberían repetir peso, subir o bajar
- notas para Hevy/rutina

---

# Lo que necesito de ustedes antes de mañana

Si me lo pasás, mejoro bastante la precisión:

1. **A qué gimnasio van** y si sabés qué máquinas tiene:
   - hack squat
   - smith
   - leg press
   - pendulum
   - belt squat
   - chest-supported row
   - T-bar row
   - calf raise
2. **Tu peso corporal actual aproximado**.
3. **Peso corporal aproximado de Andrés**.
4. Si alguno tiene:
   - dolor lumbar
   - rodilla
   - hombro
   - codo
   - muñeca
   - hipertensión / mareos / historial cardíaco
5. Para vos: si querés, puedo mirar tu historial de Hevy ejercicio por ejercicio para sugerirte cargas iniciales más finas.
6. Para Andrés: si no tiene historial, arrancamos 100% por RIR y técnica.

---

# Mi decisión final

Mañana hagan **solo Full Body W1D1** como calibración real. No intenten calcular todo el programa.

Prioridad:

1. elegir variantes seguras
2. registrar cargas
3. respetar RIR
4. no quedar destruidos
5. dejar Hevy bien cargado

Después de la sesión me mandás los sets o los leo desde Hevy si quedó sincronizado, y les preparo Día 2 con pesos sugeridos.


[nachlakes] Ok. Me podes crear la rutina en Hevy porfa? No sobreescribas rutinas viejas de Hevy. Crea una folder nueva y agrega las rutinas. Avisame una vez finalizado. USA context7 U otro sistema de apis knowledge para saber Bien lo que estas haciendo, o consulta los api docs de Hevy. Avisame si necesitas algo más.


Explanation:
**Planning API actions**

I need to check the API documentation because the skill mentions that the CLI supports creating folders and routines. It might require me to inspect the API schema too. I'll utilize web extraction for this and read through the CLI documentation. I want to create folders and routines carefully without overwriting anything. I also need exercise IDs for that, so I plan to search for heavy exercises and script creating routines from a spreadsheet. The API seems to require exercise_template_id, so I’ll seek existing names for substitutions and find any missing IDs using the CLI exercises search.

Consultar docs/API/skill Hevy para formato create-folder/create-routine

Mapear ejercicios del Min-Max 4x a exercise_template_id de Hevy

Crear folder nueva sin tocar rutinas existentes

Crear 4 rutinas Semana 1/Min-Max en Hevy y verificar


Swagger UI

Swagger UI

Explore

```
 0.0.1
```

```
OAS 3.0
```

## Hevy API Docs

```
  Welcome to Hevy's public API! We're just starting to roll this out and depending on your
  feedback, we'll be adding more features and endpoints. Also, we make no guarantees that
  we won't completely change the structure or abandon the project entirely so use it at
  your own risk. Currently, this API is only available to Hevy Pro users. You can get your
  key on our web app at https://hevy.com/settings?developer. If you have any questions,
  please reach out to pedro@hevyapp.com 🤝.

```

### Workouts

GET

/v1/workouts

Get a paginated list of workouts

POST

/v1/workouts

Create a new workout

GET

/v1/workouts/count

Get the total number of workouts on the account

GET

/v1/workouts/events

Retrieve a paged list of workout events (updates or deletes) since a given date. Events are ordered from newest to oldest. The intention is to allow clients to keep their local cache of workouts up to date without having to fetch the entire list of workouts.

GET

/v1/workouts/{workoutId}

Get a single workout’s complete details by the workoutId

PUT

/v1/workouts/{workoutId}

Update an existing workout

### Users

GET

/v1/user/info

Get user info

### Routines

GET

/v1/routines

Get a paginated list of routines

POST

/v1/routines

Create a new routine

GET

/v1/routines/{routineId}

Get a routine by its Id

PUT

/v1/routines/{routineId}

Update an existing routine

### ExerciseTemplates

GET

/v1/exercise_templates

Get a paginated list of exercise templates available on the account.

POST

/v1/exercise_templates

Create a new custom exercise template.

GET

/v1/exercise_templates/{exerciseTemplateId}

Get a single exercise template by id.

### RoutineFolders

GET

/v1/routine_folders

Get a paginated list of routine folders available on the account.

POST

/v1/routine_folders

Create a new routine folder. The folder will be created at index 0, and all other folders will have their indexes incremented.

GET

/v1/routine_folders/{folderId}

Get a single routine folder by id.

### ExerciseHistory

GET

/v1/exercise_history/{exerciseTemplateId}

Get exercise history for a specific exercise template

### Measurements

GET

/v1/body_measurements

Get a paginated list of body measurements for the authenticated user

POST

/v1/body_measurements

Create a body measurement entry for a given date. Returns 409 if an entry already exists for that date.

GET

/v1/body_measurements/{date}

Get a single body measurement by date

PUT

/v1/body_measurements/{date}

Update an existing body measurement entry for a given date. All fields are overwritten; omitted fields are set to null.

#### Schemas

PostWorkoutsRequestSet

PostWorkoutsRequestExercise

PostWorkoutsRequestBody

PostRoutinesRequestSet

PostRoutinesRequestExercise

PostRoutinesRequestBody

PutRoutinesRequestSet

PutRoutinesRequestExercise

PutRoutinesRequestBody

PostRoutineFolderRequestBody

BodyMeasurement

PutBodyMeasurement

Set

Exercise

ExerciseHistoryEntry

CustomExerciseType

MuscleGroup

EquipmentCategory

ExerciseTemplate

CreateCustomExerciseRequestBody

RoutineFolder

Routine

UserInfo

UserInfoResponse

Workout

UpdatedWorkout

DeletedWorkout

PaginatedWorkoutEvents


     1|/**
     2| * Hevy API Client
     3| *
     4| * REST client for the Hevy workout tracking API.
     5| * Docs: https://api.hevyapp.com/docs/
     6| */
     7|
     8|import type {
     9|  HevyConfig,
    10|} from "./config.js";
    11|import type {
    12|  Workout,
    13|  WorkoutsResponse,
    14|  WorkoutCountResponse,
    15|  WorkoutEventsResponse,
    16|  Routine,
    17|  RoutinesResponse,
    18|  RoutineFolder,
    19|  RoutineFoldersResponse,
    20|  ExerciseTemplate,
    21|  ExerciseTemplatesResponse,
    22|  ExerciseHistoryResponse,
    23|  CreateWorkoutRequest,
    24|  CreateRoutineRequest,
    25|  UpdateRoutineRequest,
    26|  CreateRoutineFolderRequest,
    27|  CreateExerciseTemplateRequest,
    28|} from "./types.js";
    29|
    30|const BASE_URL = "https://api.hevyapp.com";
    31|
    32|export class HevyClient {
    33|  private apiKey: string;
    34|
    35|  constructor(config: HevyConfig) {
    36|    this.apiKey = config.apiKey;
    37|  }
    38|
    39|  /**
    40|   * Make an authenticated request to the Hevy API
    41|   */
    42|  private async request<T>(
    43|    endpoint: string,
    44|    options: RequestInit = {}
    45|  ): Promise<T> {
    46|    const url = `${BASE_URL}${endpoint}`;
    47|    const headers: Record<string, string> = {
    48|      "api-key": this.apiKey,
    49|      "Content-Type": "application/json",
    50|      ...(options.headers as Record<string, string>),
    51|    };
    52|
    53|    const response = await fetch(url, {
    54|      ...options,
    55|      headers,
    56|    });
    57|
    58|    if (!response.ok) {
    59|      const text = await response.text();
    60|      throw new Error(`Hevy API error ${response.status}: ${text}`);
    61|    }
    62|
    63|    // Handle empty responses (e.g., 204 No Content)
    64|    const contentLength = response.headers.get("content-length");
    65|    if (contentLength === "0" || response.status === 204) {
    66|      return {} as T;
    67|    }
    68|
    69|    return response.json() as Promise<T>;
    70|  }
    71|
    72|  // ============================================
    73|  // Workouts
    74|  // ============================================
    75|
    76|  /**
    77|   * Get paginated list of workouts
    78|   * @param page Page number (1-indexed)
    79|   * @param pageSize Items per page (max 10)
    80|   */
    81|  async getWorkouts(page = 1, pageSize = 10): Promise<WorkoutsResponse> {
    82|    const params = new URLSearchParams({
    83|      page: String(page),
    84|      pageSize: String(Math.min(pageSize, 10)),
    85|    });
    86|    return this.request<WorkoutsResponse>(`/v1/workouts?${params}`);
    87|  }
    88|
    89|  /**
    90|   * Get all workouts (auto-paginate)
    91|   */
    92|  async getAllWorkouts(): Promise<Workout[]> {
    93|    const allWorkouts: Workout[] = [];
    94|    let page = 1;
    95|    let pageCount = 1;
    96|
    97|    do {
    98|      const response = await this.getWorkouts(page, 10);
    99|      allWorkouts.push(...response.workouts);
   100|      pageCount = response.page_count;
   101|      page++;
   102|    } while (page <= pageCount);
   103|
   104|    return allWorkouts;
   105|  }
   106|
   107|  /**
   108|   * Get a single workout by ID
   109|   */
   110|  async getWorkout(workoutId: string): Promise<Workout> {
   111|    // API may return { workout: {...} } wrapper
   112|    const response = await this.request<Workout | { workout: Workout }>(`/v1/workouts/${workoutId}`);
   113|    return 'workout' in response ? response.workout : response;
   114|  }
   115|
   116|  /**
   117|   * Get total workout count
   118|   */
   119|  async getWorkoutCount(): Promise<number> {
   120|    const response = await this.request<WorkoutCountResponse>("/v1/workouts/count");
   121|    return response.workout_count;
   122|  }
   123|
   124|  /**
   125|   * Get workout events since a date (for syncing)
   126|   * @param since ISO 8601 date string
   127|   */
   128|  async getWorkoutEvents(since: string, page = 1, pageSize = 10): Promise<WorkoutEventsResponse> {
   129|    const params = new URLSearchParams({
   130|      page: String(page),
   131|      pageSize: String(Math.min(pageSize, 10)),
   132|      since,
   133|    });
   134|    return this.request<WorkoutEventsResponse>(`/v1/workouts/events?${params}`);
   135|  }
   136|
   137|  /**
   138|   * Create a new workout
   139|   */
   140|  async createWorkout(workout: CreateWorkoutRequest): Promise<Workout> {
   141|    return this.request<Workout>("/v1/workouts", {
   142|      method: "POST",
   143|      body: JSON.stringify(workout),
   144|    });
   145|  }
   146|
   147|  /**
   148|   * Update an existing workout
   149|   */
   150|  async updateWorkout(workoutId: string, workout: CreateWorkoutRequest): Promise<Workout> {
   151|    return this.request<Workout>(`/v1/workouts/${workoutId}`, {
   152|      method: "PUT",
   153|      body: JSON.stringify(workout),
   154|    });
   155|  }
   156|
   157|  // ============================================
   158|  // Routines
   159|  // ============================================
   160|
   161|  /**
   162|   * Get paginated list of routines
   163|   */
   164|  async getRoutines(page = 1, pageSize = 10): Promise<RoutinesResponse> {
   165|    const params = new URLSearchParams({
   166|      page: String(page),
   167|      pageSize: String(Math.min(pageSize, 10)),
   168|    });
   169|    return this.request<RoutinesResponse>(`/v1/routines?${params}`);
   170|  }
   171|
   172|  /**
   173|   * Get all routines (auto-paginate)
   174|   */
   175|  async getAllRoutines(): Promise<Routine[]> {
   176|    const allRoutines: Routine[] = [];
   177|    let page = 1;
   178|    let pageCount = 1;
   179|
   180|    do {
   181|      const response = await this.getRoutines(page, 10);
   182|      allRoutines.push(...response.routines);
   183|      pageCount = response.page_count;
   184|      page++;
   185|    } while (page <= pageCount);
   186|
   187|    return allRoutines;
   188|  }
   189|
   190|  /**
   191|   * Get a single routine by ID
   192|   */
   193|  async getRoutine(routineId: string): Promise<Routine> {
   194|    const response = await this.request<{ routine: Routine }>(`/v1/routines/${routineId}`);
   195|    return response.routine;
   196|  }
   197|
   198|  /**
   199|   * Create a new routine
   200|   */
   201|  async createRoutine(routine: CreateRoutineRequest): Promise<Routine> {
   202|    const response = await this.request<{ routine: Routine[] }>("/v1/routines", {
   203|      method: "POST",
   204|      body: JSON.stringify(routine),
   205|    });
   206|    // API returns { routine: [Routine] }
   207|    return response.routine[0];
   208|  }
   209|
   210|  /**
   211|   * Update an existing routine
   212|   */
   213|  async updateRoutine(routineId: string, routine: UpdateRoutineRequest): Promise<Routine> {
   214|    return this.request<Routine>(`/v1/routines/${routineId}`, {
   215|      method: "PUT",
   216|      body: JSON.stringify(routine),
   217|    });
   218|  }
   219|
   220|  // ============================================
   221|  // Routine Folders
   222|  // ============================================
   223|
   224|  /**
   225|   * Get paginated list of routine folders
   226|   */
   227|  async getRoutineFolders(page = 1, pageSize = 10): Promise<RoutineFoldersResponse> {
   228|    const params = new URLSearchParams({
   229|      page: String(page),
   230|      pageSize: String(Math.min(pageSize, 10)),
   231|    });
   232|    return this.request<RoutineFoldersResponse>(`/v1/routine_folders?${params}`);
   233|  }
   234|
   235|  /**
   236|   * Get all routine folders (auto-paginate)
   237|   */
   238|  async getAllRoutineFolders(): Promise<RoutineFolder[]> {
   239|    const allFolders: RoutineFolder[] = [];
   240|    let page = 1;
   241|    let pageCount = 1;
   242|
   243|    do {
   244|      const response = await this.getRoutineFolders(page, 10);
   245|      // API returns routine_folders or routines depending on if empty
   246|      const folders = response.routine_folders ?? response.routines ?? [];
   247|      allFolders.push(...folders);
   248|      pageCount = response.page_count;
   249|      page++;
   250|    } while (page <= pageCount);
   251|
   252|    return allFolders;
   253|  }
   254|
   255|  /**
   256|   * Get a single routine folder by ID
   257|   */
   258|  async getRoutineFolder(folderId: number): Promise<RoutineFolder> {
   259|    return this.request<RoutineFolder>(`/v1/routine_folders/${folderId}`);
   260|  }
   261|

Use offset=261 to continue reading (showing 1-260 of 392 lines)


   260|  }
   261|
   262|  /**
   263|   * Create a new routine folder
   264|   */
   265|  async createRoutineFolder(folder: CreateRoutineFolderRequest): Promise<RoutineFolder> {
   266|    return this.request<RoutineFolder>("/v1/routine_folders", {
   267|      method: "POST",
   268|      body: JSON.stringify(folder),
   269|    });
   270|  }
   271|
   272|  // ============================================
   273|  // Exercise Templates
   274|  // ============================================
   275|
   276|  /**
   277|   * Get paginated list of exercise templates
   278|   */
   279|  async getExerciseTemplates(page = 1, pageSize = 10): Promise<ExerciseTemplatesResponse> {
   280|    const params = new URLSearchParams({
   281|      page: String(page),
   282|      pageSize: String(Math.min(pageSize, 10)),
   283|    });
   284|    return this.request<ExerciseTemplatesResponse>(`/v1/exercise_templates?${params}`);
   285|  }
   286|
   287|  /**
   288|   * Get all exercise templates (auto-paginate)
   289|   * Note: This can be a lot of data - Hevy has hundreds of built-in exercises
   290|   */
   291|  async getAllExerciseTemplates(): Promise<ExerciseTemplate[]> {
   292|    const allTemplates: ExerciseTemplate[] = [];
   293|    let page = 1;
   294|    let pageCount = 1;
   295|
   296|    do {
   297|      const response = await this.getExerciseTemplates(page, 10);
   298|      allTemplates.push(...response.exercise_templates);
   299|      pageCount = response.page_count;
   300|      page++;
   301|    } while (page <= pageCount);
   302|
   303|    return allTemplates;
   304|  }
   305|
   306|  /**
   307|   * Get a single exercise template by ID
   308|   */
   309|  async getExerciseTemplate(templateId: string): Promise<ExerciseTemplate> {
   310|    return this.request<ExerciseTemplate>(`/v1/exercise_templates/${templateId}`);
   311|  }
   312|
   313|  /**
   314|   * Create a custom exercise template
   315|   */
   316|  async createExerciseTemplate(template: CreateExerciseTemplateRequest): Promise<ExerciseTemplate> {
   317|    return this.request<ExerciseTemplate>("/v1/exercise_templates", {
   318|      method: "POST",
   319|      body: JSON.stringify(template),
   320|    });
   321|  }
   322|
   323|  // ============================================
   324|  // Exercise History
   325|  // ============================================
   326|
   327|  /**
   328|   * Get exercise history for a specific exercise template
   329|   * Returns all sets ever performed for this exercise
   330|   */
   331|  async getExerciseHistory(templateId: string, page = 1, pageSize = 10): Promise<ExerciseHistoryResponse> {
   332|    const params = new URLSearchParams({
   333|      page: String(page),
   334|      pageSize: String(Math.min(pageSize, 10)),
   335|    });
   336|    return this.request<ExerciseHistoryResponse>(`/v1/exercise_history/${templateId}?${params}`);
   337|  }
   338|
   339|  /**
   340|   * Get all exercise history for a template (auto-paginate)
   341|   */
   342|  async getAllExerciseHistory(templateId: string): Promise<ExerciseHistoryResponse["exercise_history"]> {
   343|    const allHistory: ExerciseHistoryResponse["exercise_history"] = [];
   344|    let page = 1;
   345|    let pageCount = 1;
   346|
   347|    do {
   348|      const response = await this.getExerciseHistory(templateId, page, 10);
   349|      allHistory.push(...response.exercise_history);
   350|

Use offset=350 to continue reading (showing 260-349 of 392 lines)


     1|// Hevy API Types - Generated from OpenAPI spec
     2|
     3|// Enums
     4|export type SetType = "warmup" | "normal" | "failure" | "dropset";
     5|
     6|export type ExerciseType =
     7|  | "weight_reps"
     8|  | "reps_only"
     9|  | "bodyweight_reps"
    10|  | "bodyweight_assisted_reps"
    11|  | "duration"
    12|  | "weight_duration"
    13|  | "distance_duration"
    14|  | "short_distance_weight";
    15|
    16|export type MuscleGroup =
    17|  | "abdominals"
    18|  | "shoulders"
    19|  | "biceps"
    20|  | "triceps"
    21|  | "forearms"
    22|  | "quadriceps"
    23|  | "hamstrings"
    24|  | "calves"
    25|  | "glutes"
    26|  | "abductors"
    27|  | "adductors"
    28|  | "lats"
    29|  | "upper_back"
    30|  | "traps"
    31|  | "lower_back"
    32|  | "chest"
    33|  | "cardio"
    34|  | "neck"
    35|  | "full_body"
    36|  | "other";
    37|
    38|export type EquipmentCategory =
    39|  | "none"
    40|  | "barbell"
    41|  | "dumbbell"
    42|  | "kettlebell"
    43|  | "machine"
    44|  | "plate"
    45|  | "resistance_band"
    46|  | "suspension"
    47|  | "other";
    48|
    49|// Core types
    50|export interface Set {
    51|  index: number;
    52|  type: SetType;
    53|  weight_kg: number | null;
    54|  reps: number | null;
    55|  distance_meters: number | null;
    56|  duration_seconds: number | null;
    57|  rpe: number | null;
    58|  custom_metric: number | null;
    59|}
    60|
    61|export interface RepRange {
    62|  start: number | null;
    63|  end: number | null;
    64|}
    65|
    66|export interface RoutineSet extends Set {
    67|  rep_range?: RepRange | null;
    68|}
    69|
    70|export interface Exercise {
    71|  index: number;
    72|  title: string;
    73|  notes: string;
    74|  exercise_template_id: string;
    75|  supersets_id: number | null;
    76|  sets: Set[];
    77|}
    78|
    79|export interface RoutineExercise {
    80|  index: number;
    81|  title: string;
    82|  rest_seconds: number | null;
    83|  notes: string;
    84|  exercise_template_id: string;
    85|  supersets_id: number | null;
    86|  sets: RoutineSet[];
    87|}
    88|
    89|export interface Workout {
    90|  id: string;
    91|  title: string;
    92|  routine_id?: string;
    93|  description: string;
    94|  start_time: string;
    95|  end_time: string;
    96|  updated_at: string;
    97|  created_at: string;
    98|  exercises: Exercise[];
    99|}
   100|
   101|export interface Routine {
   102|  id: string;
   103|  title: string;
   104|  folder_id: number | null;
   105|  updated_at: string;
   106|  created_at: string;
   107|  notes?: string;
   108|  exercises: RoutineExercise[];
   109|}
   110|
   111|export interface RoutineFolder {
   112|  id: number;
   113|  index: number;
   114|  title: string;
   115|  updated_at: string;
   116|  created_at: string;
   117|}
   118|
   119|export interface ExerciseTemplate {
   120|  id: string;
   121|  title: string;
   122|  type: ExerciseType;
   123|  primary_muscle_group: MuscleGroup;
   124|  secondary_muscle_groups: MuscleGroup[];
   125|  is_custom: boolean;
   126|}
   127|
   128|export interface ExerciseHistoryEntry {
   129|  workout_id: string;
   130|  workout_title: string;
   131|  workout_start_time: string;
   132|  workout_end_time: string;
   133|  exercise_template_id: string;
   134|  weight_kg: number | null;
   135|  reps: number | null;
   136|  distance_meters: number | null;
   137|  duration_seconds: number | null;
   138|  rpe: number | null;
   139|  custom_metric: number | null;
   140|  set_type: SetType;
   141|}
   142|
   143|// Paginated responses
   144|export interface PaginatedResponse<T> {
   145|  page: number;
   146|  page_count: number;
   147|}
   148|
   149|export interface WorkoutsResponse extends PaginatedResponse<Workout> {
   150|  workouts: Workout[];
   151|}
   152|
   153|export interface RoutinesResponse extends PaginatedResponse<Routine> {
   154|  routines: Routine[];
   155|}
   156|
   157|export interface ExerciseTemplatesResponse extends PaginatedResponse<ExerciseTemplate> {
   158|  exercise_templates: ExerciseTemplate[];
   159|}
   160|
   161|export interface RoutineFoldersResponse extends PaginatedResponse<RoutineFolder> {
   162|  routine_folders?: RoutineFolder[];
   163|  // API sometimes returns 'routines' key for empty results
   164|  routines?: RoutineFolder[];
   165|}
   166|
   167|export interface ExerciseHistoryResponse extends PaginatedResponse<ExerciseHistoryEntry> {
   168|  exercise_history: ExerciseHistoryEntry[];
   169|}
   170|
   171|export interface WorkoutCountResponse {
   172|  workout_count: number;
   173|}
   174|
   175|// Workout events (for sync)
   176|export interface UpdatedWorkoutEvent {
   177|  type: "updated";
   178|  workout: Workout;
   179|}
   180|
   181|export interface DeletedWorkoutEvent {
   182|  type: "deleted";
   183|  id: string;
   184|  deleted_at: string;
   185|}
   186|
   187|export type WorkoutEvent = UpdatedWorkoutEvent | DeletedWorkoutEvent;
   188|
   189|export interface WorkoutEventsResponse extends PaginatedResponse<WorkoutEvent> {
   190|  events: WorkoutEvent[];
   191|}
   192|
   193|// Request types for creating/updating
   194|export interface CreateSetRequest {
   195|  type: SetType;
   196|  weight_kg?: number | null;
   197|  reps?: number | null;
   198|  distance_meters?: number | null;
   199|  duration_seconds?: number | null;
   200|  custom_metric?: number | null;
   201|  rpe?: number | null;
   202|  rep_range?: RepRange | null;
   203|}
   204|
   205|export interface CreateExerciseRequest {
   206|  exercise_template_id: string;
   207|  superset_id?: number | null;
   208|  notes?: string | null;
   209|  rest_seconds?: number | null;
   210|  sets: CreateSetRequest[];
   211|}
   212|
   213|export interface CreateWorkoutRequest {
   214|  workout: {
   215|    title: string;
   216|    description?: string | null;
   217|    start_time: string;
   218|    end_time: string;
   219|    is_private?: boolean;
   220|    exercises: CreateExerciseRequest[];
   221|

Use offset=221 to continue reading (showing 1-220 of 255 lines)


   221|  };
   222|}
   223|
   224|export interface CreateRoutineRequest {
   225|  routine: {
   226|    title: string;
   227|    folder_id?: number | null;
   228|    notes?: string;
   229|    exercises: CreateExerciseRequest[];
   230|  };
   231|}
   232|
   233|export interface UpdateRoutineRequest {
   234|  routine: {
   235|    title: string;
   236|    notes?: string | null;
   237|    exercises: CreateExerciseRequest[];
   238|  };
   239|}
   240|
   241|export interface CreateRoutineFolderRequest {
   242|  routine_folder: {
   243|    title: string;
   244|  };
   245|}
   246|
   247|export interface CreateExerciseTemplateRequest {
   248|  exercise: {
   249|    title: string;
   250|    exercise_type: ExerciseType;
   251|    equipment_category: EquipmentCategory;
   252|    muscle_group: MuscleGroup;
   253|    other_muscles?: MuscleGroup[];
   254|  };
   255|}
   256|


Need script fetch all exercises and fuzzy map. Then create. Could use direct API. Need HEVY_API_KEY from bw? /tmp candidate exists but maybe okay. Use no display.

terminal

bash -lc 'export HEVY_API_KEY="$(cat /tmp/hevy_candidate)"; hevy exercises --json > /tmp/hevy_exercises.json; python3 - <<"PY"
import json,re
ex=json.load(open('/tmp/hevy_exercises.json'))
print(type(ex), len(ex) if isinstance(ex,list) else ex.keys())
if not isinstance(ex,list): ex=ex.get('exercise_templates', ex)
queries=["Lying Leg Curl","Squat","Barbell Incline Press","Incline DB Y-Raise","Pull-Up","Standing Calf Raise","Close-Grip Lat Pulldown","Chest-Supported T-Bar Row","Machine Shrug","Machine Chest Press","High-Cable Lateral Raise","1-Arm Reverse Pec Deck","Cable Crunch","Leg Extension","Barbell RDL","Machine Hip Thrust","Leg Press","Bayesian Cable Curl","Overhead Cable Triceps Extension","Modified Zottman Curl","Cable Triceps Kickback","DB Wrist Curl","DB Wrist Extension","Alternating DB Curl","Machine Lateral Raise","Dead Hang"]
for q in queries:
    toks=[t for t in re.sub(r'[^a-z0-9 ]',' ',q.lower()).split() if t not in {'db','barbell','machine','cable','1','arm','your','choice','wide','grip','high','close','supported'}]
    scored=[]
    for e in ex:
        title=e['title'].lower()
        score=sum(1 for t in toks if t in title)
        if q.lower() in title: score+=10
        if score: scored.append((score,e['title'],e['id'],e.get('type'),e.get('primary_muscle_group')))
    print('\nQ',q,'toks',toks)
    for row in sorted(scored, reverse=True)[:8]: print(row)
PY'


Output:
],: line 8: warning: here-document at line 1 delimited by end-of-file (wanted `PY')
  File "<stdin>", line 2
    ex=json.load(open(/tmp/hevy_exercises.json))
                      ^
SyntaxError: invalid syntax


Quoting issue. Use write temp script.

Quoting issue. Use write temp script.

import json,re
ex=json.load(open('/tmp/hevy_exercises.json'))
print(type(ex), len(ex) if isinstance(ex,list) else ex.keys())
if not isinstance(ex,list): ex=ex.get('exercise_templates', ex)
queries=["Lying Leg Curl","Squat","Barbell Incline Press","Incline DB Y-Raise","Pull-Up","Standing Calf Raise","Close-Grip Lat Pulldown","Chest-Supported T-Bar Row","Machine Shrug","Machine Chest Press","High-Cable Lateral Raise","1-Arm Reverse Pec Deck","Cable Crunch","Leg Extension","Barbell RDL","Machine Hip Thrust","Leg Press","Bayesian Cable Curl","Overhead Cable Triceps Extension","Modified Zottman Curl","Cable Triceps Kickback","DB Wrist Curl","DB Wrist Extension","Alternating DB Curl","Machine Lateral Raise","Dead Hang"]
stop={'db','barbell','machine','cable','1','arm','your','choice','wide','grip','high','close','supported','standing'}
for q in queries:
    toks=[t for t in re.sub(r'[^a-z0-9 ]',' ',q.lower()).split() if t not in stop]
    scored=[]
    for e in ex:
        title=e['title'].lower()
        score=sum(1 for t in toks if t in title)
        if q.lower().replace('-',' ') in title.replace('-',' '): score+=10
        if score: scored.append((score,e['title'],e['id'],e.get('type'),e.get('primary_muscle_group')))
    print('\nQ',q,'toks',toks)
    for row in sorted(scored, key=lambda x:(x[0], x[1]), reverse=True)[:10]: print(row)


bash -lc 'export HEVY_API_KEY="$(cat /tmp/hevy_candidate)"; hevy exercises --json > /tmp/hevy_exercises.json; /usr/bin/python3 /tmp/map_hevy.py'


Output:
Traceback (most recent call last):
  File "/tmp/map_hevy.py", line 2, in <module>
    ex=json.load(open('/tmp/hevy_exercises.json'))
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/json/__init__.py", line 293, in load
    return loads(fp.read(),
           ^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/json/__init__.py", line 346, in loads
    return _default_decoder.decode(s)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/json/decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/json/decoder.py", line 355, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)


head -20 /tmp/hevy_exercises.json; hevy exercises --help


Output:
Fetching exercise templates...
[
  {
    "id": "3BC06AD3",
    "title": "21s Bicep Curl",
    "type": "weight_reps",
    "primary_muscle_group": "biceps",
    "secondary_muscle_groups": [],
    "equipment": "barbell",
    "is_custom": false
  },
  {
    "id": "B4F2FF72",
    "title": "Ab Scissors",
    "type": "reps_only",
    "primary_muscle_group": "abdominals",
    "secondary_muscle_groups": [],
    "equipment": "none",
    "is_custom": false
  },
Usage: hevy exercises [options]

List exercise templates

Options:
  -s, --search <query>  Search by name
  --custom              Show only custom exercises
  --muscle <group>      Filter by muscle group
  --json                Output as JSON
  -h, --help            display help for command


python3 - <<'PY'
p='/tmp/hevy_exercises.json'
s=open(p).read();
idx=s.find('[')
open('/tmp/hevy_exercises_clean.json','w').write(s[idx:])
PY
sed -i "s|/tmp/hevy_exercises.json|/tmp/hevy_exercises_clean.json|" /tmp/map_hevy.py
/usr/bin/python3 /tmp/map_hevy.py


Output:
<class 'list'> 435

Q Lying Leg Curl toks ['lying', 'leg', 'curl']
(13, 'Lying Leg Curl (Machine)', 'B8127AD1', 'weight_reps', 'hamstrings')
(2, 'Standing Leg Curls', '6120CAAB', 'weight_reps', 'hamstrings')
(2, 'Seated Leg Curl (Machine)', '11A123F3', 'weight_reps', 'hamstrings')
(2, 'Lying Neck Curls (Weighted)', '31B8DA0F', 'weight_reps', 'neck')
(2, 'Lying Neck Curls', '22FEDCA4', 'reps_only', 'neck')
(2, 'Lying Leg Raise', '09C9F635', 'reps_only', 'abdominals')
(1, 'Zottman Curl (Dumbbell)', '123EE239', 'weight_reps', 'biceps')
(1, 'Waiter Curl (Dumbbell)', '72297E8C', 'weight_reps', 'biceps')
(1, 'Straight Leg Deadlift', '2A48E443', 'weight_reps', 'hamstrings')
(1, 'Spider Curl (Dumbbell)', '90427D4A', 'weight_reps', 'biceps')

Q Squat toks ['squat']
(11, 'Zercher Squat', '40C6A9FC', 'weight_reps', 'quadriceps')
(11, 'Sumo Squat (Kettlebell)', '5E10D0E6', 'weight_reps', 'quadriceps')
(11, 'Sumo Squat (Dumbbell)', '05293BCA', 'weight_reps', 'quadriceps')
(11, 'Sumo Squat (Barbell)', '6622E5A0', 'weight_reps', 'quadriceps')
(11, 'Sumo Squat', '3F5F8D40', 'reps_only', 'quadriceps')
(11, 'Squat Row', 'A8814C06', 'weight_reps', 'full_body')
(11, 'Squat (Suspension)', 'B82D6418', 'reps_only', 'quadriceps')
(11, 'Squat (Smith Machine)', 'DDCC3821', 'weight_reps', 'quadriceps')
(11, 'Squat (Machine)', 'CC35A01F', 'weight_reps', 'quadriceps')
(11, 'Squat (Dumbbell)', 'DCFF3E9F', 'weight_reps', 'quadriceps')

Q Barbell Incline Press toks ['incline', 'press']
(2, 'Incline Chest Press (Machine)', 'FBF92739', 'weight_reps', 'chest')
(2, 'Incline Bench Press (Smith Machine)', '3A6FA3D1', 'weight_reps', 'chest')
(2, 'Incline Bench Press (Dumbbell)', '07B38369', 'weight_reps', 'chest')
(2, 'Incline Bench Press (Barbell)', '50DFDFAB', 'weight_reps', 'chest')
(1, 'Wide-Elbow Triceps Press (Dumbbell)', 'B31EB524', 'weight_reps', 'triceps')
(1, 'Triceps Pressdown', 'CDC472B1', 'weight_reps', 'triceps')
(1, 'Standing Military Press (Barbell)', '073032BB', 'weight_reps', 'shoulders')
(1, 'Single Leg Press (Machine)', '3FD83744', 'weight_reps', 'quadriceps')
(1, 'Single Arm Landmine Press (Barbell)', '1837BA23', 'weight_reps', 'shoulders')
(1, 'Shoulder Press (Machine Plates)', '059E835D', 'weight_reps', 'shoulders')

Q Incline DB Y-Raise toks ['incline', 'y', 'raise']
(13, 'Incline DB Y-Raise', 'afbbf3e8-66d9-4622-8b2e-55dfcdab16f1', 'weight_reps', 'shoulders')
(2, 'Lying Leg Raise', '09C9F635', 'reps_only', 'abdominals')
(2, 'Lying Knee Raise', 'BD5935CF', 'reps_only', 'abdominals')
(2, 'Incline Chest Fly (Dumbbell)', 'D3E2AB55', 'weight_reps', 'chest')
(2, 'Chest Supported Y Raise (Dumbbell)', 'F21D5693', 'weight_reps', 'shoulders')
(2, 'Bicycle Crunch Raised Legs', '594450D2', 'reps_only', 'abdominals')
(1, 'Yoga', '8C9D2928', 'duration', 'full_body')
(1, 'Standing Military Press (Barbell)', '073032BB', 'weight_reps', 'shoulders')
(1, 'Standing Calf Raise (Smith)', 'AA52E8D2', 'weight_reps', 'calves')
(1, 'Standing Calf Raise (Machine)', 'E05C2C38', 'weight_reps', 'calves')

Q Pull-Up toks ['pull', 'up']
(12, 'Wide Pull Up', '7C50F118', 'reps_only', 'lats')
(12, 'Sternum Pull up (Gironda)', 'EE2938D1', 'reps_only', 'lats')
(12, 'Scapular Pull Ups', 'C7AE420A', 'reps_only', 'upper_back')
(12, 'Ring Pull Up', '5F09F0FC', 'reps_only', 'lats')
(12, 'Pull Up (Weighted)', '729237D1', 'bodyweight_weighted', 'lats')
(12, 'Pull Up (Band)', '56808FD2', 'bodyweight_assisted', 'lats')
(12, 'Pull Up (Assisted)', '2C37EC5E', 'bodyweight_assisted', 'lats')
(12, 'Pull Up', '1B2B1E7C', 'reps_only', 'lats')
(12, 'Negative Pull Up', 'F60BDDF8', 'bodyweight_weighted', 'lats')
(12, 'Kipping Pull Up', 'A91838C0', 'reps_only', 'lats')

Q Standing Calf Raise toks ['calf', 'raise']
(12, 'Standing Calf Raise (Smith)', 'AA52E8D2', 'weight_reps', 'calves')
(12, 'Standing Calf Raise (Machine)', 'E05C2C38', 'weight_reps', 'calves')
(12, 'Standing Calf Raise (Dumbbell)', '6DA40660', 'weight_reps', 'calves')
(12, 'Standing Calf Raise (Barbell)', 'E53CCBE5', 'weight_reps', 'calves')
(12, 'Standing Calf Raise', '06745E58', 'reps_only', 'calves')
(12, 'Single Leg Standing Calf Raise (Machine)', 'B05C2C29', 'weight_reps', 'calves')
(12, 'Single Leg Standing Calf Raise (Dumbbell)', '5DA40761', 'weight_reps', 'calves')
(12, 'Single Leg Standing Calf Raise (Barbell)', 'E51CCBE5', 'weight_reps', 'calves')
(12, 'Single Leg Standing Calf Raise', '238BA231', 'reps_only', 'calves')
(2, 'Seated Calf Raise', '062AB91A', 'weight_reps', 'calves')

Q Close-Grip Lat Pulldown toks ['lat', 'pulldown']
(2, 'Straight Arm Lat Pulldown (Cable)', 'D2387AB1', 'weight_reps', 'lats')
(2, 'Single Arm Lat Pulldown', '2EE45F81', 'weight_reps', 'lats')
(2, 'Reverse Grip Lat Pulldown (Cable)', '046E25A2', 'weight_reps', 'lats')
(2, 'Lat Pulldown - Close Grip (Cable)', '4E5257DE', 'weight_reps', 'lats')
(2, 'Lat Pulldown (Machine)', '473CF5B8', 'weight_reps', 'lats')
(2, 'Lat Pulldown (Cable)', '6A6C31A5', 'weight_reps', 'lats')
(2, 'Lat Pulldown (Band)', 'D2FE7B2E', 'weight_reps', 'lats')
(1, 'Single Arm Lateral Raise (Cable)', 'DE68C825', 'weight_reps', 'shoulders')
(1, 'Shoulder Press (Machine Plates)', '059E835D', 'weight_reps', 'shoulders')
(1, 'Seated Lateral Raise (Dumbbell)', '9372FFAA', 'weight_reps', 'shoulders')

Q Chest-Supported T-Bar Row toks ['chest', 't', 'bar', 'row']
(3, 'Upright Row (Barbell)', '7AB9A362', 'weight_reps', 'shoulders')
(3, 'T Bar Row', '08A2974E', 'weight_reps', 'upper_back')
(3, 'Seated Cable Row - Bar Wide Grip', 'C3BCABB3', 'weight_reps', 'upper_back')
(3, 'Seated Cable Row - Bar Grip', 'F1D60854', 'weight_reps', 'upper_back')
(3, 'Chest Supported Incline Row (Dumbbell)', '914F3A96', 'weight_reps', 'upper_back')
(3, 'Bent Over Row (Barbell)', '55E6546F', 'weight_reps', 'upper_back')
(2, 'Upright Row (Dumbbell)', '797F0782', 'weight_reps', 'shoulders')
(2, 'Upright Row (Cable)', '286C1D0B', 'weight_reps', 'shoulders')
(2, 'Triceps Extension (Barbell)', '2F8D3067', 'weight_reps', 'triceps')
(2, 'Toes to Bar', 'B94E35E1', 'reps_only', 'abdominals')

Q Machine Shrug toks ['shrug']
(1, 'Shrug (Smith Machine)', '742E5BD5', 'weight_reps', 'traps')
(1, 'Shrug (Machine)', '19A38071', 'weight_reps', 'traps')
(1, 'Shrug (Dumbbell)', 'ABEC557F', 'weight_reps', 'traps')
(1, 'Shrug (Cable)', 'FFABC123', 'weight_reps', 'traps')
(1, 'Shrug (Barbell)', '0B841777', 'weight_reps', 'traps')
(1, 'Jump Shrug', 'BA3DE542', 'weight_reps', 'full_body')

Q Machine Chest Press toks ['chest', 'press']
(2, 'Iso-Lateral Chest Press (Machine)', '24706DCD', 'weight_reps', 'chest')
(2, 'Incline Chest Press (Machine)', 'FBF92739', 'weight_reps', 'chest')
(2, 'Chest Press (Machine)', '7EB3F7C3', 'weight_reps', 'chest')
(2, 'Chest Press (Band)', 'EAC7D9C5', 'weight_reps', 'chest')
(1, 'Wide-Elbow Triceps Press (Dumbbell)', 'B31EB524', 'weight_reps', 'triceps')
(1, 'Triceps Pressdown', 'CDC472B1', 'weight_reps', 'triceps')
(1, 'Standing Military Press (Barbell)', '073032BB', 'weight_reps', 'shoulders')
(1, 'Single Leg Press (Machine)', '3FD83744', 'weight_reps', 'quadriceps')
(1, 'Single Arm Landmine Press (Barbell)', '1837BA23', 'weight_reps', 'shoulders')
(1, 'Shoulder Press (Machine Plates)', '059E835D', 'weight_reps', 'shoulders')

Q High-Cable Lateral Raise toks ['lateral', 'raise']
(2, 'Single Arm Lateral Raise (Cable)', 'DE68C825', 'weight_reps', 'shoulders')
(2, 'Seated Lateral Raise (Dumbbell)', '9372FFAA', 'weight_reps', 'shoulders')
(2, 'Lateral Raise (Machine)', 'D5D0354D', 'weight_reps', 'shoulders')
(2, 'Lateral Raise (Dumbbell)', '422B08F1', 'weight_reps', 'shoulders')
(2, 'Lateral Raise (Cable)', 'BE289E45', 'weight_reps', 'shoulders')
(2, 'Lateral Raise (Band)', 'DF200976', 'weight_reps', 'shoulders')
(2, 'Lateral Leg Raises', 'DC59D143', 'reps_only', 'glutes')
(1, 'Standing Calf Raise (Smith)', 'AA52E8D2', 'weight_reps', 'calves')
(1, 'Standing Calf Raise (Machine)', 'E05C2C38', 'weight_reps', 'calves')
(1, 'Standing Calf Raise (Dumbbell)', '6DA40660', 'weight_reps', 'calves')

Q 1-Arm Reverse Pec Deck toks ['reverse', 'pec', 'deck']
(2, 'Butterfly (Pec Deck)', '9DCE2D64', 'weight_reps', 'chest')
(1, 'Reverse Plank', '99B49684', 'duration', 'abdominals')
(1, 'Reverse Lunge (Dumbbell)', 'FFDA283B', 'weight_reps', 'quadriceps')
(1, 'Reverse Lunge (Barbell)', '818BA121', 'weight_reps', 'quadriceps')
(1, 'Reverse Lunge', 'C284D923', 'reps_only', 'quadriceps')
(1, 'Reverse Hyperextension', '7C0028C3', 'weight_reps', 'glutes')
(1, 'Reverse Grip Lat Pulldown (Cable)', '046E25A2', 'weight_reps', 'lats')
(1, 'Reverse Grip Concentration Curl', '1FF4097B', 'weight_reps', 'biceps')
(1, 'Reverse Fly Single Arm (Cable)', '9264ADA1', 'weight_reps', 'shoulders')
(1, 'Reverse Curl (Dumbbell)', 'B567DD46', 'weight_reps', 'biceps')

Q Cable Crunch toks ['crunch']
(11, 'Cable Crunch', '23A48484', 'weight_reps', 'abdominals')
(1, 'Reverse Crunch', '7952B5CD', 'reps_only', 'abdominals')
(1, 'Oblique Crunch', 'DBE341AA', 'reps_only', 'abdominals')
(1, 'Decline Crunch (Weighted)', 'B2398CD1', 'bodyweight_weighted', 'abdominals')
(1, 'Decline Crunch', 'BC10A922', 'reps_only', 'abdominals')
(1, 'Crunch (Weighted)', 'D928C232', 'bodyweight_weighted', 'abdominals')
(1, 'Crunch (Machine)', 'EB43ADD4', 'weight_reps', 'abdominals')
(1, 'Crunch', 'DCF3B31B', 'reps_only', 'abdominals')
(1, 'Bicycle Crunch Raised Legs', '594450D2', 'reps_only', 'abdominals')
(1, 'Bicycle Crunch', 'A41C7261', 'reps_only', 'abdominals')

Q Leg Extension toks ['leg', 'extension']
(12, 'Single Leg Extensions', '629AE73D', 'weight_reps', 'quadriceps')
(12, 'Leg Extension (Machine)', '75A4F6C4', 'weight_reps', 'quadriceps')
(1, 'Triceps Extension (Suspension)', '6182EE83', 'reps_only', 'triceps')
(1, 'Triceps Extension (Machine)', '3092FADD', 'weight_reps', 'triceps')
(1, 'Triceps Extension (Dumbbell)', '3765684D', 'weight_reps', 'triceps')
(1, 'Triceps Extension (Cable)', '21310F5F', 'weight_reps', 'triceps')
(1, 'Triceps Extension (Barbell)', '2F8D3067', 'weight_reps', 'triceps')
(1, 'Straight Leg Deadlift', '2A48E443', 'weight_reps', 'hamstrings')
(1, 'Standing Leg Curls', '6120CAAB', 'weight_reps', 'hamstrings')
(1, 'Single Leg Standing Calf Raise (Machine)', 'B05C2C29', 'weight_reps', 'calves')

Q Barbell RDL toks ['rdl']

Q Machine Hip Thrust toks ['hip', 'thrust']
(2, 'Single Leg Hip Thrust (Dumbbell)', 'D1CD146F', 'weight_reps', 'glutes')
(2, 'Single Leg Hip Thrust', '487B3755', 'reps_only', 'glutes')
(2, 'Hip Thrust (Smith Machine)', '291ABA92', 'weight_reps', 'glutes')
(2, 'Hip Thrust (Machine)', '68CE0B9B', 'weight_reps', 'glutes')
(2, 'Hip Thrust (Barbell)', 'D57C2EC7', 'weight_reps', 'glutes')
(2, 'Hip Thrust', '92B8C7E1', 'reps_only', 'glutes')
(1, 'Thruster (Kettlebell)', '10313AFD', 'weight_reps', 'full_body')
(1, 'Thruster (Barbell)', '90E506D5', 'weight_reps', 'full_body')
(1, 'Hip Adduction (Machine)', '8BEBFED6', 'weight_reps', 'adductors')
(1, 'Hip Abduction (Machine)', 'F4B4C6EE', 'weight_reps', 'abductors')

Q Leg Press toks ['leg', 'press']
(12, 'Single Leg Press (Machine)', '3FD83744', 'weight_reps', 'quadriceps')
(12, 'Leg Press Horizontal (Machine)', '0EB695C9', 'weight_reps', 'quadriceps')
(12, 'Leg Press (Machine)', 'C7973E0E', 'weight_reps', 'quadriceps')
(1, 'Wide-Elbow Triceps Press (Dumbbell)', 'B31EB524', 'weight_reps', 'triceps')
(1, 'Triceps Pressdown', 'CDC472B1', 'weight_reps', 'triceps')
(1, 'Straight Leg Deadlift', '2A48E443', 'weight_reps', 'hamstrings')
(1, 'Standing Military Press (Barbell)', '073032BB', 'weight_reps', 'shoulders')
(1, 'Standing Leg Curls', '6120CAAB', 'weight_reps', 'hamstrings')
(1, 'Single Leg Standing Calf Raise (Machine)', 'B05C2C29', 'weight_reps', 'calves')
(1, 'Single Leg Standing Calf Raise (Dumbbell)', '5DA40761', 'weight_reps', 'calves')

Q Bayesian Cable Curl toks ['bayesian', 'curl']
(1, 'Zottman Curl (Dumbbell)', '123EE239', 'weight_reps', 'biceps')
(1, 'Waiter Curl (Dumbbell)', '72297E8C', 'weight_reps', 'biceps')
(1, 'Standing Leg Curls', '6120CAAB', 'weight_reps', 'hamstrings')
(1, 'Spider Curl (Dumbbell)', '90427D4A', 'weight_reps', 'biceps')
(1, 'Spider Curl (Barbell)', '2348AB72', 'weight_reps', 'biceps')
(1, 'Single Arm Curl (Cable)', 'B33B526E', 'weight_reps', 'biceps')
(1, 'Seated Palms Up Wrist Curl', '1006DF48', 'weight_reps', 'forearms')
(1, 'Seated Leg Curl (Machine)', '11A123F3', 'weight_reps', 'hamstrings')
(1, 'Seated Incline Curl (Dumbbell)', '8BAB2735', 'weight_reps', 'biceps')
(1, 'Rope Cable Curl', '234897AB', 'weight_reps', 'biceps')

Q Overhead Cable Triceps Extension toks ['overhead', 'triceps', 'extension']
(3, 'Overhead Triceps Extension (Cable)', 'B5EFBF9C', 'weight_reps', 'triceps')
(2, 'Triceps Extension (Suspension)', '6182EE83', 'reps_only', 'triceps')
(2, 'Triceps Extension (Machine)', '3092FADD', 'weight_reps', 'triceps')
(2, 'Triceps Extension (Dumbbell)', '3765684D', 'weight_reps', 'triceps')
(2, 'Triceps Extension (Cable)', '21310F5F', 'weight_reps', 'triceps')
(2, 'Triceps Extension (Barbell)', '2F8D3067', 'weight_reps', 'triceps')
(1, 'Wide-Elbow Triceps Press (Dumbbell)', 'B31EB524', 'weight_reps', 'triceps')
(1, 'Triceps Rope Pushdown', '94B7239B', 'weight_reps', 'triceps')
(1, 'Triceps Pushdown', '93A552C6', 'weight_reps', 'triceps')
(1, 'Triceps Pressdown', 'CDC472B1', 'weight_reps', 'triceps')

Q Modified Zottman Curl toks ['modified', 'zottman', 'curl']
(2, 'Zottman Curl (Dumbbell)', '123EE239', 'weight_reps', 'biceps')
(1, 'Waiter Curl (Dumbbell)', '72297E8C', 'weight_reps', 'biceps')
(1, 'Standing Leg Curls', '6120CAAB', 'weight_reps', 'hamstrings')
(1, 'Spider Curl (Dumbbell)', '90427D4A', 'weight_reps', 'biceps')
(1, 'Spider Curl (Barbell)', '2348AB72', 'weight_reps', 'biceps')
(1, 'Single Arm Curl (Cable)', 'B33B526E', 'weight_reps', 'biceps')
(1, 'Seated Palms Up Wrist Curl', '1006DF48', 'weight_reps', 'forearms')
(1, 'Seated Leg Curl (Machine)', '11A123F3', 'weight_reps', 'hamstrings')
(1, 'Seated Incline Curl (Dumbbell)', '8BAB2735', 'weight_reps', 'biceps')
(1, 'Rope Cable Curl', '234897AB', 'weight_reps', 'biceps')

Q Cable Triceps Kickback toks ['triceps', 'kickback']
(2, 'Triceps Kickback (Dumbbell)', '6127A3AD', 'weight_reps', 'triceps')
(2, 'Triceps Kickback (Cable)', 'EC3B69A3', 'weight_reps', 'triceps')
(1, 'Wide-Elbow Triceps Press (Dumbbell)', 'B31EB524', 'weight_reps', 'triceps')
(1, 'Triceps Rope Pushdown', '94B7239B', 'weight_reps', 'triceps')
(1, 'Triceps Pushdown', '93A552C6', 'weight_reps', 'triceps')
(1, 'Triceps Pressdown', 'CDC472B1', 'weight_reps', 'triceps')
(1, 'Triceps Extension (Suspension)', '6182EE83', 'reps_only', 'triceps')
(1, 'Triceps Extension (Machine)', '3092FADD', 'weight_reps', 'triceps')
(1, 'Triceps Extension (Dumbbell)', '3765684D', 'weight_reps', 'triceps')
(1, 'Triceps Extension (Cable)', '21310F5F', 'weight_reps', 'triceps')

Q DB Wrist Curl toks ['wrist', 'curl']
(2, 'Seated Palms Up Wrist Curl', '1006DF48', 'weight_reps', 'forearms')
(2, 'Behind the Back Bicep Wrist Curl (Barbell)', 'DDB29047', 'weight_reps', 'forearms')
(1, 'Zottman Curl (Dumbbell)', '123EE239', 'weight_reps', 'biceps')
(1, 'Wrist Roller', '0E4523F4', 'weight_reps', 'forearms')
(1, 'Waiter Curl (Dumbbell)', '72297E8C', 'weight_reps', 'biceps')
(1, 'Standing Leg Curls', '6120CAAB', 'weight_reps', 'hamstrings')
(1, 'Spider Curl (Dumbbell)', '90427D4A', 'weight_reps', 'biceps')
(1, 'Spider Curl (Barbell)', '2348AB72', 'weight_reps', 'biceps')
(1, 'Single Arm Curl (Cable)', 'B33B526E', 'weight_reps', 'biceps')
(1, 'Seated Wrist Extension (Barbell)', '9202CC23', 'weight_reps', 'forearms')

Q DB Wrist Extension toks ['wrist', 'extension']
(2, 'Seated Wrist Extension (Barbell)', '9202CC23', 'weight_reps', 'forearms')
(1, 'Wrist Roller', '0E4523F4', 'weight_reps', 'forearms')
(1, 'Triceps Extension (Suspension)', '6182EE83', 'reps_only', 'triceps')
(1, 'Triceps Extension (Machine)', '3092FADD', 'weight_reps', 'triceps')
(1, 'Triceps Extension (Dumbbell)', '3765684D', 'weight_reps', 'triceps')
(1, 'Triceps Extension (Cable)', '21310F5F', 'weight_reps', 'triceps')
(1, 'Triceps Extension (Barbell)', '2F8D3067', 'weight_reps', 'triceps')
(1, 'Single Leg Extensions', '629AE73D', 'weight_reps', 'quadriceps')
(1, 'Single Arm Tricep Extension (Dumbbell)', '8347DFD1', 'weight_reps', 'triceps')
(1, 'Seated Palms Up Wrist Curl', '1006DF48', 'weight_reps', 'forearms')

Q Alternating DB Curl toks ['alternating', 'curl']
(1, 'Zottman Curl (Dumbbell)', '123EE239', 'weight_reps', 'biceps')
(1, 'Waiter Curl (Dumbbell)', '72297E8C', 'weight_reps', 'biceps')
(1, 'Standing Leg Curls', '6120CAAB', 'weight_reps', 'hamstrings')
(1, 'Spider Curl (Dumbbell)', '90427D4A', 'weight_reps', 'biceps')
(1, 'Spider Curl (Barbell)', '2348AB72', 'weight_reps', 'biceps')
(1, 'Single Arm Curl (Cable)', 'B33B526E', 'weight_reps', 'biceps')
(1, 'Seated Palms Up Wrist Curl', '1006DF48', 'weight_reps', 'forearms')
(1, 'Seated Leg Curl (Machine)', '11A123F3', 'weight_reps', 'hamstrings')
(1, 'Seated Incline Curl (Dumbbell)', '8BAB2735', 'weight_reps', 'biceps')
(1, 'Rope Cable Curl', '234897AB', 'weight_reps', 'biceps')

Q Machine Lateral Raise toks ['lateral', 'raise']
(2, 'Single Arm Lateral Raise (Cable)', 'DE68C825', 'weight_reps', 'shoulders')
(2, 'Seated Lateral Raise (Dumbbell)', '9372FFAA', 'weight_reps', 'shoulders')
(2, 'Lateral Raise (Machine)', 'D5D0354D', 'weight_reps', 'shoulders')
(2, 'Lateral Raise (Dumbbell)', '422B08F1', 'weight_reps', 'shoulders')
(2, 'Lateral Raise (Cable)', 'BE289E45', 'weight_reps', 'shoulders')
(2, 'Lateral Raise (Band)', 'DF200976', 'weight_reps', 'shoulders')
(2, 'Lateral Leg Raises', 'DC59D143', 'reps_only', 'glutes')
(1, 'Standing Calf Raise (Smith)', 'AA52E8D2', 'weight_reps', 'calves')
(1, 'Standing Calf Raise (Machine)', 'E05C2C38', 'weight_reps', 'calves')
(1, 'Standing Calf Raise (Dumbbell)', '6DA40660', 'weight_reps', 'calves')

Q Dead Hang toks ['dead', 'hang']
(12, 'Dead Hang', 'B9380898', 'duration', 'upper_back')
(1, 'Sumo Deadlift', 'D20D7BBE', 'weight_reps', 'glutes')
(1, 'Straight Leg Deadlift', '2A48E443', 'weight_reps', 'hamstrings')
(1, 'Single Leg Romanian Deadlift (Dumbbell)', '937292AB', 'weight_reps', 'hamstrings')
(1, 'Single Leg Romanian Deadlift (Barbell)', '93472AC1', 'weight_reps', 'hamstrings')
(1, 'Romanian Deadlift (Dumbbell)', '72CFFAD5', 'weight_reps', 'hamstrings')
(1, 'Romanian Deadlift (Barbell)', '2B4B7310', 'weight_reps', 'hamstrings')
(1, 'Hanging Leg Raise', 'F8356514', 'reps_only', 'abdominals')
(1, 'Hanging Knee Raise', '08590920', 'reps_only', 'abdominals')
(1, 'Hang Snatch', 'F4E77594', 'weight_reps', 'full_body')


Explanation:
**Evaluating exercise equipment**

I’m considering creating or choosing IDs for my workout plan. For something missing, maybe I could create a custom Bayesian option? I think it might be better to use the Single Arm Curl Cable. I’m also thinking about the modified Zottman Curl with dumbbells, and I should probably use the Romanian Deadlift with a barbell. For the Reverse Pec Deck, maybe I should go with the Reverse Fly Machine? I need to search for the exact options.

python3 - <<'PY'
import json
ex=json.load(open('/tmp/hevy_exercises_clean.json'))
for term in ['Romanian','Reverse Fly','Rear Delt','Pec Deck','Bayesian','Alternating','Wrist Extension','Wrist Curl','Incline Bench Press']:
 print('\nTERM',term)
 for e in ex:
  if term.lower() in e['title'].lower(): print(e['title'], e['id'], e['type'], e.get('primary_muscle_group'), e.get('equipment'))
PY


Output:
TERM Romanian
Romanian Deadlift (Barbell) 2B4B7310 weight_reps hamstrings barbell
Romanian Deadlift (Dumbbell) 72CFFAD5 weight_reps hamstrings dumbbell
Single Leg Romanian Deadlift (Barbell) 93472AC1 weight_reps hamstrings barbell
Single Leg Romanian Deadlift (Dumbbell) 937292AB weight_reps hamstrings dumbbell

TERM Reverse Fly
Chest Supported Reverse Fly (Dumbbell) B582299E weight_reps shoulders dumbbell
Rear Delt Reverse Fly (Cable) C315DC2A weight_reps shoulders machine
Rear Delt Reverse Fly (Dumbbell) E5988A0A weight_reps shoulders dumbbell
Rear Delt Reverse Fly (Machine) D8281C62 weight_reps shoulders machine
Reverse Fly Single Arm (Cable) 9264ADA1 weight_reps shoulders machine

TERM Rear Delt
Rear Delt Reverse Fly (Cable) C315DC2A weight_reps shoulders machine
Rear Delt Reverse Fly (Dumbbell) E5988A0A weight_reps shoulders dumbbell
Rear Delt Reverse Fly (Machine) D8281C62 weight_reps shoulders machine

TERM Pec Deck
Butterfly (Pec Deck) 9DCE2D64 weight_reps chest machine

TERM Bayesian

TERM Alternating

TERM Wrist Extension
Seated Wrist Extension (Barbell) 9202CC23 weight_reps forearms barbell

TERM Wrist Curl
Behind the Back Bicep Wrist Curl (Barbell) DDB29047 weight_reps forearms barbell
Seated Palms Up Wrist Curl 1006DF48 weight_reps forearms dumbbell

TERM Incline Bench Press
Incline Bench Press (Barbell) 50DFDFAB weight_reps chest barbell
Incline Bench Press (Dumbbell) 07B38369 weight_reps chest dumbbell
Incline Bench Press (Smith Machine) 3A6FA3D1 weight_reps chest machine


Explanation:
**Planning routines creation**

I need to create a folder for routines, possibly including four routines with empty weights, reps, and rep range. I'll use a POST request and interact with the API directly. It might be good to set a duration for dead hang and include notes about RIR. I want to confirm that there's no existing duplicate folder, and the title could be something like "Jeff Nippard Min-Max 4x (2026-05)." I’ll ensure to use the API safely.

import json, os, pathlib, subprocess, requests, sys, time

# Load key from existing Bitwarden candidate file produced earlier; don't print it.
api_key = pathlib.Path('/tmp/hevy_candidate').read_text().strip()
BASE='https://api.hevyapp.com'
headers={'api-key': api_key, 'Content-Type':'application/json'}

def req(method, path, **kwargs):
    r=requests.request(method, BASE+path, headers=headers, timeout=60, **kwargs)
    if not r.ok:
        print('ERROR', method, path, r.status_code, r.text[:1000])
        r.raise_for_status()
    return r.json() if r.text.strip() else {}

# Check current folders/routines to avoid overwriting and avoid exact duplicate title.
def get_all(path, key):
    out=[]; page=1
    while True:
        data=req('GET', f'{path}?page={page}&pageSize=10')
        out.extend(data.get(key) or data.get('routines') or [])
        if page >= data.get('page_count',1): break
        page += 1
    return out

folders=get_all('/v1/routine_folders','routine_folders')
routines=get_all('/v1/routines','routines')
base_title='Jeff Nippard Min-Max 4x — Start 2026-05'
title=base_title
existing_titles={f.get('title') for f in folders}
if title in existing_titles:
    # Never reuse/overwrite: create a unique folder.
    n=2
    while f'{base_title} ({n})' in existing_titles: n+=1
    title=f'{base_title} ({n})'

folder=req('POST','/v1/routine_folders',json={'routine_folder':{'title':title}})
folder_id=folder.get('id') or folder.get('routine_folder',{}).get('id')
if not folder_id:
    print('Unexpected folder response', folder); sys.exit(1)

# Chosen template IDs from Hevy exercise catalog. Where program gave variants, choose safer/default machine where useful.
ID={
 'Lying Leg Curl (Machine)':'B8127AD1',
 'Squat (Smith Machine)':'DDCC3821',
 'Incline Bench Press (Barbell)':'50DFDFAB',
 'Incline DB Y-Raise':'afbbf3e8-66d9-4622-8b2e-55dfcdab16f1',
 'Pull Up (Assisted)':'2C37EC5E',
 'Standing Calf Raise (Machine)':'E05C2C38',
 'Lat Pulldown - Close Grip (Cable)':'4E5257DE',
 'T Bar Row':'08A2974E',
 'Shrug (Machine)':'19A38071',
 'Chest Press (Machine)':'7EB3F7C3',
 'Single Arm Lateral Raise (Cable)':'DE68C825',
 'Rear Delt Reverse Fly (Machine)':'D8281C62',
 'Cable Crunch':'23A48484',
 'Leg Extension (Machine)':'75A4F6C4',
 'Romanian Deadlift (Barbell)':'2B4B7310',
 'Hip Thrust (Machine)':'68CE0B9B',
 'Leg Press (Machine)':'C7973E0E',
 'Single Arm Curl (Cable)':'B33B526E',
 'Overhead Triceps Extension (Cable)':'B5EFBF9C',
 'Zottman Curl (Dumbbell)':'123EE239',
 'Triceps Kickback (Cable)':'EC3B69A3',
 'Seated Palms Up Wrist Curl':'1006DF48',
 'Seated Wrist Extension (Barbell)':'9202CC23',
 'Bicep Curl (Dumbbell)':'028B0D7D',
 'Lateral Raise (Machine)':'D5D0354D',
 'Dead Hang':'B9380898',
}

def set_normal(rep_range=None):
    d={'type':'normal','weight_kg':None,'reps':None,'distance_meters':None,'duration_seconds':None,'custom_metric':None,'rpe':None}
    if rep_range:
        a,b=map(int, rep_range.split('-'))
        d['rep_range']={'start':a,'end':b}
    return d

def set_duration():
    return {'type':'normal','weight_kg':None,'reps':None,'distance_meters':None,'duration_seconds':None,'custom_metric':None,'rpe':None}

def ex(template_title, program_name, sets, reps, rest, rir, notes=''):
    st=[set_normal(reps) for _ in range(sets)] if reps!='N/A' else [set_duration() for _ in range(sets)]
    rir_txt=', '.join(str(x) for x in rir)
    n=f'Min-Max W1. Objetivo: {sets} set(s), {reps} reps, RIR: {rir_txt}. {notes}'.strip()
    return {'exercise_template_id':ID[template_title], 'notes':n, 'rest_seconds':rest, 'sets':st}

# rest seconds: use upper bound-ish for timer practicality.
routines_def=[
 ('Min-Max 4x W1D1 — Full Body Calibration', [
  ex('Lying Leg Curl (Machine)','Lying Leg Curl',2,'6-8',90,[1,0],'Setear máquina para máximo estiramiento; que no se levante la cadera.'),
  ex('Squat (Smith Machine)','Squat (Your Choice)',2,'6-8',240,[3,2],'Variante segura inicial. Si usan hack/pendulum/belt squat, sustituir manualmente.'),
  ex('Incline Bench Press (Barbell)','Barbell Incline Press',2,'6-8',240,[2,1],'Banco 30–45°, pausa 1s abajo. Pueden sustituir por Smith/DB incline.'),
  ex('Incline DB Y-Raise','Incline DB Y-Raise',1,'8-10',90,[0],'Peso muy bajo; levantar en forma de Y.'),
  ex('Pull Up (Assisted)','Pull-Up (Wide Grip)',2,'6-8',180,[2,1],'Usar asistencia o lat pulldown si hace falta; ROM completo.'),
  ex('Standing Calf Raise (Machine)','Standing Calf Raise',1,'6-8',90,[0],'Pausa 1–2s abajo; sin rebotes.'),
 ]),
 ('Min-Max 4x W1D2 — Upper Calibration', [
  ex('Lat Pulldown - Close Grip (Cable)','Close-Grip Lat Pulldown',2,'8-10',180,[2,1],'Inclinarse ~15°, codos abajo, escapulas juntas.'),
  ex('T Bar Row','Chest-Supported T-Bar Row',2,'8-10',180,[2,1],'Si hay chest-supported machine row, usar esa.'),
  ex('Shrug (Machine)','Machine Shrug',1,'6-8',90,[1],'Pensar hombros a las orejas; straps si hace falta.'),
  ex('Chest Press (Machine)','Machine Chest Press',2,'8-10',240,[2,1],'Pausa 1s abajo manteniendo tensión.'),
  ex('Single Arm Lateral Raise (Cable)','High-Cable Lateral Raise',2,'8-10',90,[1,0],'Cable a altura de cadera; dejar cruzar levemente la línea media.'),
  ex('Rear Delt Reverse Fly (Machine)','1-Arm Reverse Pec Deck',1,'8-10',90,[0],'Sustitución segura por reverse fly machine.'),
  ex('Cable Crunch','Cable Crunch',2,'6-8',90,[1,0],'Redondear zona lumbar al crunch; foco en abdomen.'),
 ]),
 ('Min-Max 4x W1D3 — Lower Calibration', [
  ex('Leg Extension (Machine)','Leg Extension',2,'8-10',90,[1,0],'Asiento atrás; mantener cadera pegada.'),
  ex('Romanian Deadlift (Barbell)','Barbell RDL',2,'6-8',180,[3,2],'Glúteos atrás, barra cerca, columna neutra. Muy conservador al inicio.'),
  ex('Hip Thrust (Machine)','Machine Hip Thrust',2,'6-8',180,[2,1],'Apretar glúteos arriba y controlar bajada.'),
  ex('Leg Press (Machine)','Leg Press',1,'6-8',180,[1],'Pies más bajos para quad; profundidad sin redondear espalda.'),
  ex('Standing Calf Raise (Machine)','Standing Calf Raise',2,'8-10',90,[1,0],'Pausa 1–2s abajo; sin rebotes.'),
 ]),
 ('Min-Max 4x W1D4 — Arms/Delts Calibration', [
  ex('Single Arm Curl (Cable)','Bayesian Cable Curl',2,'6-8',90,[1,0],'Usar cable detrás del cuerpo si se puede; sustitución si no existe Bayesian exacto.'),
  ex('Overhead Triceps Extension (Cable)','Overhead Cable Triceps Extension',2,'8-10',90,[1,0],'Sentir estiramiento profundo en tríceps.'),
  ex('Zottman Curl (Dumbbell)','Modified Zottman Curl',1,'8-10',90,[0],'Hammer subiendo, supinado bajando si hacen variante modificada.'),
  ex('Triceps Kickback (Cable)','Cable Triceps Kickback',2,'8-10',90,[1,0],'Brazo superior detrás del torso.'),
  ex('Seated Palms Up Wrist Curl','DB Wrist Curl',2,'8-10',90,[1,0],'Controlado.'),
  ex('Seated Wrist Extension (Barbell)','DB Wrist Extension',2,'8-10',90,[1,0],'Sustitución por extensión de muñeca disponible.'),
  ex('Bicep Curl (Dumbbell)','Alternating DB Curl',1,'6-8',90,[0],'Alternar brazos manualmente.'),
  ex('Lateral Raise (Machine)','Machine Lateral Raise',2,'8-10',90,[1,0],'Foco en deltoide lateral.'),
  ex('Dead Hang','Dead Hang (optional)',2,'N/A',90,[0,0],'Opcional; loguear duración.'),
 ]),
]

created=[]
for title_r, exercises in routines_def:
    # Do not overwrite; if title exists anywhere, suffix timestamp.
    final_title=title_r
    if final_title in {r.get('title') for r in routines}:
        final_title=f'{title_r} — {int(time.time())}'
    payload={'routine':{'title':final_title,'folder_id':folder_id,'notes':'Jeff Nippard Min-Max 4x — Semana 1 / calibración. Pesos vacíos para completar en Hevy. Usar RIR y técnica; no testear 1RM real.','exercises':exercises}}
    routine=req('POST','/v1/routines',json=payload)
    created.append({'title':routine.get('title',final_title),'id':routine.get('id'),'exercises':len(exercises)})

# Verify folder and routines exist
verified=[]
for c in created:
    rr=req('GET',f"/v1/routines/{c['id']}") if c.get('id') else {}
    rou=rr.get('routine',rr)
    verified.append({'title':rou.get('title'), 'id':rou.get('id'), 'folder_id':rou.get('folder_id'), 'exercises':len(rou.get('exercises') or [])})

print(json.dumps({'folder':{'title':title,'id':folder_id}, 'created':verified}, indent=2, ensure_ascii=False))


Output:
ERROR POST /v1/routines 400 {"error":"\"routine.exercises[0].sets[0].rpe\" is not allowed"}
Traceback (most recent call last):
  File "/tmp/create_minmax_hevy.py", line 133, in <module>
    routine=req('POST','/v1/routines',json=payload)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/create_minmax_hevy.py", line 12, in req
    r.raise_for_status()
  File "/usr/lib/python3/dist-packages/requests/models.py", line 1021, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 400 Client Error: Bad Request for url: https://api.hevyapp.com/v1/routines


Explanation:
**Organizing folder structures**

I need to make sure I avoid duplicates while creating a new folder. There's a folder with no routines, so if I use the same title, it might create a duplicate. The user said a new folder is okay, but I want to avoid having extra empty folders. I should check if I can use the existing empty folder instead. I also need to ensure I'm getting the latest folder and managing sets correctly.

patch

title=base_title
existing_titles={f.get('title') for f in folders}
if title in existing_titles:
    # Never reuse/overwrite: create a unique folder.
    n=2
    while f'{base_title} ({n})' in existing_titles: n+=1
    title=f'{base_title} ({n})'

folder=req('POST','/v1/routine_folders',json={'routine_folder':{'title':title}})
folder_id=folder.get('id') or folder.get('routine_folder',{}).get('id')
if not folder_id:
    print('Unexpected folder response', folder); sys.exit(1)

title=base_title
# If a previous attempt created the folder but no Min-Max routines, reuse it instead of making clutter.
existing_minmax=[f for f in folders if (f.get('title') or '').startswith(base_title)]
folder_id=None
if existing_minmax:
    folder_id=sorted(existing_minmax, key=lambda f: f.get('created_at',''))[-1].get('id')
    title=sorted(existing_minmax, key=lambda f: f.get('created_at',''))[-1].get('title')
else:
    folder=req('POST','/v1/routine_folders',json={'routine_folder':{'title':title}})
    folder_id=folder.get('id') or folder.get('routine_folder',{}).get('id')
    if not folder_id:
        print('Unexpected folder response', folder); sys.exit(1)


--- a//tmp/create_minmax_hevy.py
+++ b//tmp/create_minmax_hevy.py
@@ -26,17 +26,17 @@
 routines=get_all('/v1/routines','routines')
 base_title='Jeff Nippard Min-Max 4x — Start 2026-05'
 title=base_title
-existing_titles={f.get('title') for f in folders}
-if title in existing_titles:
-    # Never reuse/overwrite: create a unique folder.
-    n=2
-    while f'{base_title} ({n})' in existing_titles: n+=1
-    title=f'{base_title} ({n})'
-
-folder=req('POST','/v1/routine_folders',json={'routine_folder':{'title':title}})
-folder_id=folder.get('id') or folder.get('routine_folder',{}).get('id')
-if not folder_id:
-    print('Unexpected folder response', folder); sys.exit(1)
+# If a previous attempt created the folder but no Min-Max routines, reuse it instead of making clutter.
+existing_minmax=[f for f in folders if (f.get('title') or '').startswith(base_title)]
+folder_id=None
+if existing_minmax:
+    folder_id=sorted(existing_minmax, key=lambda f: f.get('created_at',''))[-1].get('id')
+    title=sorted(existing_minmax, key=lambda f: f.get('created_at',''))[-1].get('title')
+else:
+    folder=req('POST','/v1/routine_folders',json={'routine_folder':{'title':title}})
+    folder_id=folder.get('id') or folder.get('routine_folder',{}).get('id')
+    if not folder_id:
+        print('Unexpected folder response', folder); sys.exit(1)

 # Chosen template IDs from Hevy exercise catalog. Where program gave variants, choose safer/default machine where useful.
 ID={


def set_normal(rep_range=None):
    d={'type':'normal','weight_kg':None,'reps':None,'distance_meters':None,'duration_seconds':None,'custom_metric':None}
    if rep_range:
        a,b=map(int, rep_range.split('-'))
        d['rep_range']={'start':a,'end':b}
    return d

def set_duration():
    return {'type':'normal','weight_kg':None,'reps':None,'distance_meters':None,'duration_seconds':None,'custom_metric':None}

def set_normal(rep_range=None):
    d={'type':'normal','weight_kg':None,'reps':None,'distance_meters':None,'duration_seconds':None,'custom_metric':None,'rpe':None}
    if rep_range:
        a,b=map(int, rep_range.split('-'))
        d['rep_range']={'start':a,'end':b}
    return d

def set_duration():
    return {'type':'normal','weight_kg':None,'reps':None,'distance_meters':None,'duration_seconds':None,'custom_metric':None,'rpe':None}


--- a//tmp/create_minmax_hevy.py
+++ b//tmp/create_minmax_hevy.py
@@ -69,14 +69,14 @@
 }

 def set_normal(rep_range=None):
-    d={'type':'normal','weight_kg':None,'reps':None,'distance_meters':None,'duration_seconds':None,'custom_metric':None,'rpe':None}
+    d={'type':'normal','weight_kg':None,'reps':None,'distance_meters':None,'duration_seconds':None,'custom_metric':None}
     if rep_range:
         a,b=map(int, rep_range.split('-'))
         d['rep_range']={'start':a,'end':b}
     return d

 def set_duration():
-    return {'type':'normal','weight_kg':None,'reps':None,'distance_meters':None,'duration_seconds':None,'custom_metric':None,'rpe':None}
+    return {'type':'normal','weight_kg':None,'reps':None,'distance_meters':None,'duration_seconds':None,'custom_metric':None}

 def ex(template_title, program_name, sets, reps, rest, rir, notes=''):
     st=[set_normal(reps) for _ in range(sets)] if reps!='N/A' else [set_duration() for _ in range(sets)]


Output:
ERROR POST /v1/routines 400 Found invalid exercise template id
Traceback (most recent call last):
  File "/tmp/create_minmax_hevy.py", line 133, in <module>
    routine=req('POST','/v1/routines',json=payload)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/create_minmax_hevy.py", line 12, in req
    r.raise_for_status()
  File "/usr/lib/python3/dist-packages/requests/models.py", line 1021, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 400 Client Error: Bad Request for url: https://api.hevyapp.com/v1/routines


Explanation:
**Identifying invalid IDs**

I need to figure out if I'm dealing with an invalid ID, possibly in the custom Incline DB Y. Maybe it relates to a lowercase UUID? To check, I should verify each ID with a GET request. It sounds like I need to use a script to tackle this. I want to ensure that I have everything I need to confirm whether these IDs are valid or not, so I'll dive into that!

terminal

/usr/bin/python3 - <<'PY'
import requests,pathlib,re,ast
text=open('/tmp/create_minmax_hevy.py').read()
api=pathlib.Path('/tmp/hevy_candidate').read_text().strip(); h={'api-key':api}
ids=re.findall(r"'([^']+)':'([^']+)'", text.split('ID={',1)[1].split('}',1)[0])
for name,id in ids:
 r=requests.get('https://api.hevyapp.com/v1/exercise_templates/'+id,headers=h,timeout=30)
 if not r.ok: print('BAD',name,id,r.status_code,r.text[:200])
PY


Output: BAD Bicep Curl (Dumbbell) 028B0D7D 404 Exercise template not found


python3 - <<'PY'
import json
ex=json.load(open('/tmp/hevy_exercises_clean.json'))
for term in ['Curl (Dumbbell)','Bicep Curl','Alternating']:
 print('\n',term)
 for e in ex:
  if term.lower() in e['title'].lower(): print(e['title'], e['id'])
PY


Output:
Curl (Dumbbell)
Bicep Curl (Dumbbell) 37FCC2BB
Hammer Curl (Dumbbell) 7E3BC8B6
Pinwheel Curl (Dumbbell) 942BAD12
Preacher Curl (Dumbbell) FAB6EB2F
Reverse Curl (Dumbbell) B567DD46
Seated Incline Curl (Dumbbell) 8BAB2735
Spider Curl (Dumbbell) 90427D4A
Waiter Curl (Dumbbell) 72297E8C
Zottman Curl (Dumbbell) 123EE239

 Bicep Curl
21s Bicep Curl 3BC06AD3
Bicep Curl (Barbell) A5AC6449
Bicep Curl (Cable) ADA8623C
Bicep Curl (Dumbbell) 37FCC2BB
Bicep Curl (Machine) AF328E3D
Bicep Curl (Suspension) 0B4C1902

 Alternating


--- a//tmp/create_minmax_hevy.py
+++ b//tmp/create_minmax_hevy.py
@@ -63,7 +63,7 @@
  'Triceps Kickback (Cable)':'EC3B69A3',
  'Seated Palms Up Wrist Curl':'1006DF48',
  'Seated Wrist Extension (Barbell)':'9202CC23',
- 'Bicep Curl (Dumbbell)':'028B0D7D',
+ 'Bicep Curl (Dumbbell)':'37FCC2BB',
  'Lateral Raise (Machine)':'D5D0354D',
  'Dead Hang':'B9380898',
 }


Output:
{
  "folder": {
    "title": "Jeff Nippard Min-Max 4x — Start 2026-05",
    "id": 2826578
  },
  "created": [
    {
      "title": null,
      "id": null,
      "folder_id": null,
      "exercises": 0
    },
    {
      "title": null,
      "id": null,
      "folder_id": null,
      "exercises": 0
    },
    {
      "title": null,
      "id": null,
      "folder_id": null,
      "exercises": 0
    },
    {
      "title": null,
      "id": null,
      "folder_id": null,
      "exercises": 0
    }
  ]
}


bash -lc 'export HEVY_API_KEY="$(cat /tmp/hevy_candidate)"; hevy routines --json > /tmp/hevy_routines_after.json; python3 - <<"PY"
import json
s=open("/tmp/hevy_routines_after.json").read(); i=s.find("["); data=json.loads(s[i:])
for r in data[:20]:
    if "Min-Max" in r.get("title","") or (r.get("folder_id")==2826578):
        print(r.get("title"), r.get("id"), r.get("folder_id"), len(r.get("exercises") or []))
PY'


Output:
Min-Max 4x W1D4 — Arms/Delts Calibration f3051fb9-6f03-440e-9320-cf8ea835fe52 2826578 9
Min-Max 4x W1D3 — Lower Calibration — 1778122488 09274657-d4dd-41ac-ad88-617454238ab1 2826578 5
Min-Max 4x W1D2 — Upper Calibration — 1778122487 4ff12149-e92e-4521-a24f-926037c3bf7c 2826578 7
Min-Max 4x W1D1 — Full Body Calibration — 1778122487 3e699dc2-f6a2-41a6-8d84-90da5d00418a 2826578 6
Min-Max 4x W1D3 — Lower Calibration d7b6da14-88c5-474e-b70a-4ae8c8a4cbb4 2826578 5
Min-Max 4x W1D2 — Upper Calibration 2164feea-1b5f-4822-adb5-aec6e58cdf88 2826578 7
Min-Max 4x W1D1 — Full Body Calibration 78e21872-61ae-46ac-b1e8-91fcdd488d9d 2826578 6


Explanation:
**Assessing duplicate routines**

It looks like I accidentally created duplicates D1-D3 due to a previous error. The system seems to have skipped existing titles from the first attempt, which has led to confusion. The user mentioned not wanting to overwrite old routines but is okay with deleting duplicates. However, the API doesn’t support a delete function. I’m considering if I can use the PUT method to rename these duplicates instead. I definitely need to clarify how to deal with these duplicates.
**Deciding on duplicate routines**

I need to clarify what the user ultimately wants for their routines. I have seven routines: D1-D3 in plain and suffixed forms, plus D4 in plain. Should I create a missing D4 suffix? Actually, maybe I should consider moving duplicates to a "DUPLICATES ignore" folder. However, I can't seem to move them or create a quarantine folder. I could update the duplicate titles but that might overwrite new duplicates instead. It could be best to keep all routines while helping the user manage clutter. Maybe there’s another way to handle unwanted duplicates.

/usr/bin/python3 - <<'PY'
import requests,pathlib,json
api=pathlib.Path('/tmp/hevy_candidate').read_text().strip(); h={'api-key':api,'Content-Type':'application/json'}
# Try DELETE only on the accidental suffixed duplicates created in this run.
ids=['3e699dc2-f6a2-41a6-8d84-90da5d00418a','4ff12149-e92e-4521-a24f-926037c3bf7c','09274657-d4dd-41ac-ad88-617454238ab1']
for id in ids:
 r=requests.delete('https://api.hevyapp.com/v1/routines/'+id,headers=h,timeout=30)
 print(id, r.status_code, r.text[:200])
PY


Output:
3e699dc2-f6a2-41a6-8d84-90da5d00418a 404 <!DOCTYPE html>

<meta charset="utf-8">
<title>Error</title>

Cannot DELETE /v1/routines/3e699dc2-f6a2-41a6-8d84-90da5d00418a

4ff12149-e92e-4521-a24f-926037c3bf7c 404 <!DOCTYPE html>

<meta charset="utf-8">
<title>Error</title>

Cannot DELETE /v1/routines/4ff12149-e92e-4521-a24f-926037c3bf7c

09274657-d4dd-41ac-ad88-617454238ab1 404 <!DOCTYPE html>

<meta charset="utf-8">
<title>Error</title>

Cannot DELETE /v1/routines/09274657-d4dd-41ac-ad88-617454238ab1


/usr/bin/python3 - <<'PY'
import requests,pathlib,json
api=pathlib.Path('/tmp/hevy_candidate').read_text().strip(); h={'api-key':api,'Content-Type':'application/json'}
id='3e699dc2-f6a2-41a6-8d84-90da5d00418a'
r=requests.get('https://api.hevyapp.com/v1/routines/'+id,headers=h,timeout=30)
print(r.status_code, r.text[:300])
data=r.json(); rou=data.get('routine',data)
# build update with same exercises, move out if accepted
payload={'routine':{'title':'ZZ IGNORE — duplicate Min-Max W1D1 (API cleanup)', 'folder_id': None, 'notes':'Duplicate created during API setup; safe to delete in Hevy app. Use the non-IGNORE Min-Max routines.', 'exercises':[]}}
# use empty exercises? API may allow? safer? let's test update duplicate only.
r2=requests.put('https://api.hevyapp.com/v1/routines/'+id,headers=h,json=payload,timeout=30)
print('PUT',r2.status_code,r2.text[:500])
PY


Output:
200 {"routine":{"id":"3e699dc2-f6a2-41a6-8d84-90da5d00418a","title":"Min-Max 4x W1D1 — Full Body Calibration — 1778122487","folder_id":2826578,"updated_at":"2026-05-07T02:54:47.520Z","created_at":"2026-05-07T02:54:47.520Z","exercises":[{"index":0,"title":"Lying Leg Curl (Machine)","notes":"Min-Max W1. O
PUT 400 {"error":"\"routine.exercises\" must contain at least 1 items"}


Explanation:
**Considering exercise routines**

I need to include the current transformed exercises, but I’m wondering if I should move out. I might need to build from the routine exercises in an API format, but not necessarily. I should verify everything and finalize my plan. Perhaps creating a folder with seven routines is the way to go. I might have to rename all three duplicates with "ZZ..." and set the folder_id to null if it’s accepted. Now, I need to transform the sets for all existing exercises.

import requests,pathlib,json
api=pathlib.Path('/tmp/hevy_candidate').read_text().strip(); h={'api-key':api,'Content-Type':'application/json'}
ids=[
('3e699dc2-f6a2-41a6-8d84-90da5d00418a','ZZ IGNORE — duplicate Min-Max W1D1'),
('4ff12149-e92e-4521-a24f-926037c3bf7c','ZZ IGNORE — duplicate Min-Max W1D2'),
('09274657-d4dd-41ac-ad88-617454238ab1','ZZ IGNORE — duplicate Min-Max W1D3'),
]
def clean_ex(e):
    out={'exercise_template_id':e['exercise_template_id'],'notes':(e.get('notes') or '')+'\nDuplicate copy; use clean routine.', 'rest_seconds':e.get('rest_seconds'), 'sets':[]}
    for s in e.get('sets') or []:
        d={'type':s.get('type','normal'),'weight_kg':s.get('weight_kg'),'reps':s.get('reps'),'distance_meters':s.get('distance_meters'),'duration_seconds':s.get('duration_seconds'),'custom_metric':s.get('custom_metric')}
        if s.get('rep_range') is not None: d['rep_range']=s['rep_range']
        out['sets'].append(d)
    return out
for id,title in ids:
    r=requests.get('https://api.hevyapp.com/v1/routines/'+id,headers=h,timeout=30)
    rou=r.json().get('routine',r.json())
    payload={'routine':{'title':title,'folder_id':None,'notes':'Duplicate created during API setup; safe to delete in Hevy app. Use the clean Min-Max routines without ZZ IGNORE.','exercises':[clean_ex(e) for e in rou.get('exercises',[])]}}
    r2=requests.put('https://api.hevyapp.com/v1/routines/'+id,headers=h,json=payload,timeout=60)
    print(id, r2.status_code, r2.text[:200])