# hevy
An async Rust client library for the [Hevy API](https://api.hevyapp.com/docs/) — the public REST API for the [Hevy](https://hevy.com) workout tracking app.
Hevy's public API is only available to **Hevy Pro** users. Grab an API key from your [developer settings](https://hevy.com/settings?developer) before using this crate.
> Per Hevy's own docs: "we make no guarantees that we won't completely change the structure or abandon the project entirely, so use it at your own risk." This crate follows the documented `0.0.1` OpenAPI spec as closely as possible and deserializes leniently where the docs are inconsistent.
## Features
- Full coverage of the documented API surface:
- **Workouts** — list, get, create, update, count, and sync via events
- **Routines** — list, get, create, update
- **Routine folders** — list, get, create
- **Exercise templates** — list, get, create custom exercises
- **Exercise history** — fetch historical sets for an exercise, with optional date filtering
- **Body measurements** — list, get, create, update
- **User info** — fetch the authenticated user's profile
- Fully async, built on [`reqwest`](https://docs.rs/reqwest) + [`tokio`](https://docs.rs/tokio)
- Typed request/response models with `serde`
- Ergonomic builders for constructing workouts and routines
- Structured error type ([`HevyError`]) distinguishing transport errors, decode errors, and API errors (with status code + message)
## Installation
```sh
cargo add hevy
```
Or add it to your `Cargo.toml` manually:
```toml
[dependencies]
hevy = "0.1"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
```
## Usage
```rust,no_run
use hevy::{CreateWorkoutRequest, HevyClient, NewWorkout, WorkoutExerciseInput, WorkoutSetInput};
#[tokio::main]
async fn main() -> hevy::Result<()> {
let client = HevyClient::new("YOUR_API_KEY");
// List workouts.
let page = client.list_workouts(Some(1), Some(5)).await?;
for workout in &page.workouts {
println!("{}: {}", workout.id, workout.title);
}
// Log a new workout.
let workout = NewWorkout::new(
"Friday Leg Day",
"2024-08-14T12:00:00Z",
"2024-08-14T12:30:00Z",
)
.with_exercise(
WorkoutExerciseInput::new("D04AC939").with_set(WorkoutSetInput::normal(100.0, 10)),
);
let created = client.create_workout(&CreateWorkoutRequest::new(workout)).await?;
println!("Created workout {}", created.id);
Ok(())
}
```
See [`examples/basic_usage.rs`](examples/basic_usage.rs) for a more complete tour (fetching your profile, listing workouts, and logging a new one). Run it with:
```sh
HEVY_API_KEY=your-key cargo run --example basic_usage
```
## Error handling
Every fallible method returns [`hevy::Result<T>`], where errors are represented by [`hevy::HevyError`]:
- `HevyError::Request` — the HTTP request itself failed (network, TLS, timeout, etc.)
- `HevyError::Decode` — the response body couldn't be parsed into the expected type
- `HevyError::Api { status, message }` — the API responded with a non-2xx status; `message` is extracted from the response's `error` field when present
- `HevyError::Config` — the client was misconfigured
## Testing
The test suite uses [`wiremock`](https://docs.rs/wiremock) to mock the Hevy API over HTTP, so no real API key or network access is required:
```sh
cargo test
```
## License
MIT