Tutorial March 26, 2026 · 8 min read

How to Build a Workout App Using a REST API

A complete guide to integrating the WorkoutX exercise database API into your fitness application. Covers authentication, fetching exercises, displaying GIF animations, and filtering by muscle group — with real code examples in JavaScript, Python, and Swift.

What You'll Build

By the end of this guide, your app will be able to: fetch a list of exercises from a REST API, filter them by body part or target muscle, display animated GIF previews for each exercise, and handle pagination for large result sets. We'll use the WorkoutX API — a free exercise database with 1,300+ exercises and GIF animations.

Prerequisites: Basic knowledge of HTTP requests and JSON. No specific framework required — examples work in vanilla JavaScript, Node.js, Python, or any language that can make HTTP requests.

Step 1: Get Your API Key

Every request to the WorkoutX API requires an API key in the X-WorkoutX-Key header. The free plan gives you 500 requests per month with no credit card required — enough to prototype and build your MVP.

  • Go to workoutxapp.com/dashboard and create a free account
  • Your API key appears instantly in the Developer Portal
  • Format: wx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Step 2: Make Your First Request

The base URL for all API calls is https://api.workoutxapp.com/v1. Pass your key in the request header. Here's a minimal example that fetches 5 exercises:

JavaScript (fetch) GET /v1/exercises
const API_KEY = 'wx_your_key_here';
const BASE_URL = 'https://api.workoutxapp.com/v1';

async function fetchExercises() {
  const response = await fetch(`${BASE_URL}/exercises?limit=5`, {
    headers: {
      'X-WorkoutX-Key': API_KEY
    }
  });

  if (!response.ok) {
    throw new Error(`API error: ${response.status}`);
  }

  const exercises = await response.json();
  // exercises is an array of exercise objects
  console.log(exercises);
  return exercises;
}

fetchExercises();
Python (requests) GET /v1/exercises
import requests

API_KEY = 'wx_your_key_here'
BASE_URL = 'https://api.workoutxapp.com/v1'

def fetch_exercises(limit=5):
    response = requests.get(
        f'{BASE_URL}/exercises',
        headers={'X-WorkoutX-Key': API_KEY},
        params={'limit': limit}
    )
    response.raise_for_status()
    return response.json()

exercises = fetch_exercises()
for ex in exercises:
    print(f"{ex['name']} — {ex['bodyPart']}")

Understanding the Response Shape

Each exercise object contains the following fields:

JSON Response
{
  "id": "0001",
  "name": "3/4 sit-up",
  "bodyPart": "waist",
  "target": "abs",
  "equipment": "body weight",
  "gifUrl": "https://cdn.workoutxapp.com/gifs/0001.gif",
  "effortLevel": "beginner",
  "caloriesBurnPerMin": 4.2,
  "instructions": [
    "Lie down on your back...",
    "Bend your knees..."
  ]
}

Step 3: Filter by Body Part

The most common use case is letting users pick a body part (chest, back, shoulders, etc.) and see relevant exercises. Use the /exercises/bodyPart/:bodyPart endpoint:

JavaScript — Filter by body part
async function getExercisesByBodyPart(bodyPart) {
  // Valid: chest, back, waist, shoulders, upper arms,
  //        lower arms, upper legs, lower legs, cardio, neck
  const url = `${BASE_URL}/exercises/bodyPart/${encodeURIComponent(bodyPart)}?limit=20`;

  const res = await fetch(url, {
    headers: { 'X-WorkoutX-Key': API_KEY }
  });

  return res.json();
}

// Example: get 20 chest exercises
const chestExercises = await getExercisesByBodyPart('chest');

Step 4: Display GIF Animations

The gifUrl field points to a hosted GIF animation for each exercise. Use lazy loading to avoid loading all GIFs at once — especially important on mobile:

HTML — Exercise card with GIF
function renderExerciseCard(exercise) {
  return `
    <div class="exercise-card">
      <img
        src="${exercise.gifUrl}"
        alt="${exercise.name} exercise animation"
        loading="lazy"
        width="320"
        height="320"
      />
      <div class="exercise-info">
        <h3>${exercise.name}</h3>
        <span class="badge">${exercise.bodyPart}</span>
        <span class="badge">${exercise.target}</span>
        <span class="badge">${exercise.equipment}</span>
        <p class="effort">${exercise.effortLevel} · ~${exercise.caloriesBurnPerMin} cal/min</p>
      </div>
    </div>
  `;
}

// Render all exercises
const container = document.getElementById('exercises');
container.innerHTML = exercises.map(renderExerciseCard).join('');

Step 5: Filter by Target Muscle

For more granular control, use the target muscle endpoint. This is useful when your users want to specifically target biceps, triceps, or quads:

JavaScript — Filter by target muscle
async function getExercisesByTarget(target) {
  // Examples: abs, biceps, triceps, quads, glutes,
  //           hamstrings, calves, deltoids, lats, pecs
  const url = `${BASE_URL}/exercises/target/${encodeURIComponent(target)}?limit=15`;
  const res = await fetch(url, { headers: { 'X-WorkoutX-Key': API_KEY }});
  return res.json();
}

// Get available target muscles
async function getTargetList() {
  const res = await fetch(`${BASE_URL}/exercises/targetList`, {
    headers: { 'X-WorkoutX-Key': API_KEY }
  });
  return res.json(); // Returns array of target muscle names
}

Step 6: Handle Pagination

The database has 1,300+ exercises. Use limit and offset to paginate results efficiently. A "Load More" pattern works well for fitness apps:

JavaScript — Pagination with "Load More"
let offset = 0;
const PAGE_SIZE = 12;

async function loadMore(bodyPart) {
  const url = `${BASE_URL}/exercises/bodyPart/${bodyPart}`
    + `?limit=${PAGE_SIZE}&offset=${offset}`;

  const res = await fetch(url, { headers: { 'X-WorkoutX-Key': API_KEY }});
  const exercises = await res.json();

  appendExercises(exercises);
  offset += PAGE_SIZE;

  // Hide button if fewer results returned than page size
  if (exercises.length < PAGE_SIZE) {
    document.getElementById('load-more').style.display = 'none';
  }
}

// Initialize on page load
loadMore('chest');

Step 7: Search Exercises by Name

Let users search for a specific exercise by name using the name query parameter on the main endpoint:

JavaScript — Search by exercise name
async function searchExercises(query) {
  const url = `${BASE_URL}/exercises/name/${encodeURIComponent(query)}`;
  const res = await fetch(url, { headers: { 'X-WorkoutX-Key': API_KEY }});
  return res.json();
}

// Debounce for search input
let searchTimeout;
document.getElementById('search').addEventListener('input', (e) => {
  clearTimeout(searchTimeout);
  searchTimeout = setTimeout(() => {
    searchExercises(e.target.value);
  }, 300);
});

Error Handling Best Practices

Always handle the common API error codes your app may encounter:

JavaScript — Robust error handling
async function apiRequest(endpoint) {
  try {
    const res = await fetch(`${BASE_URL}${endpoint}`, {
      headers: { 'X-WorkoutX-Key': API_KEY }
    });

    switch (res.status) {
      case 200: return res.json();
      case 401: throw new Error('Invalid API key');
      case 429: throw new Error('Rate limit exceeded — upgrade your plan');
      case 404: throw new Error('Exercise not found');
      default:  throw new Error(`API error ${res.status}`);
    }
  } catch (err) {
    console.error('WorkoutX API:', err.message);
    throw err;
  }
}

Complete Minimal App

Here's a working single-file HTML app that ties everything together — a body part picker with exercise cards and lazy-loaded GIFs:

index.html — Complete workout app
<!DOCTYPE html>
<html lang="en">
<head>
  <title>My Workout App</title>
  <style>
    .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px,1fr)); gap: 16px; }
    .card { border: 1px solid #eee; border-radius: 8px; overflow: hidden; }
    .card img { width: 100%; height: 200px; object-fit: cover; }
    .card-body { padding: 12px; }
    .filters { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 24px; }
    button { padding: 8px 16px; border-radius: 6px; cursor: pointer; border: 1px solid #ddd; }
    button.active { background: #4f46e5; color: white; border-color: #4f46e5; }
  </style>
</head>
<body>
  <h1>Exercise Library</h1>
  <div class="filters" id="filters"></div>
  <div class="grid" id="exercises"></div>

  <script>
    const KEY = 'wx_your_key_here';
    const BASE = 'https://api.workoutxapp.com/v1';
    const PARTS = ['chest','back','shoulders','waist','upper arms','upper legs'];

    async function load(part) {
      const res = await fetch(
        `${BASE}/exercises/bodyPart/${encodeURIComponent(part)}?limit=12`,
        { headers: { 'X-WorkoutX-Key': KEY } }
      );
      const data = await res.json();
      document.getElementById('exercises').innerHTML = data.map(ex => `
        <div class="card">
          <img src="${ex.gifUrl}" alt="${ex.name}" loading="lazy" />
          <div class="card-body">
            <h3>${ex.name}</h3>
            <p>${ex.target} · ${ex.equipment}</p>
          </div>
        </div>
      `).join('');
    }

    // Render filter buttons
    document.getElementById('filters').innerHTML = PARTS.map(p => `
      <button onclick="load('${p}')">${p}</button>
    `).join('');

    load('chest'); // default
  </script>
</body>
</html>

Next Steps

You now have a working exercise browser. Here's where to go from here:

  • Add workout builder: Let users save exercises into a "workout" and track sets/reps with localStorage
  • Filter by equipment: Use /exercises/equipment/:equipment for home vs. gym workouts
  • Sort by calories: Add sortMethod=caloriesBurnPerMin&sortOrder=descending to rank exercises by caloric burn
  • Search by name: Implement /exercises/name/:name for fuzzy name search
  • Upgrade your plan: When you're ready to scale, the Pro plan gives you 10,000 requests/month for $15.99

Full API reference: See all available endpoints, parameters, and response schemas in the WorkoutX API Documentation.

fitness_center

Ready to start building?

Get your free API key in 30 seconds. 500 requests/month, no credit card required.

Get Free API Key arrow_forward