# Personal API key

Canonical HTML: https://effortsignal.com/personal-api-key/

Last content update: 2026-10-22

<!-- Personal keys use the account's existing settings and ingest API; keeping their guide
     separate prevents athletes from following company registration and OAuth instructions. -->
## Connect your own scripts

A personal API key lets your script or data exporter work with your own EffortSignal account. You can upload completed workouts, sleep and daily measurements, or read saved training data. Choose the permissions each script needs when creating its key.

You create the key in your account settings. Application registration, callback URLs and an OAuth exchange are not needed. For a product that connects other athletes' accounts, use [API for Companies](/developers/).

[Create a key](#create-a-key) · [Choose permissions](#choose-permissions) · [Use your key](#use-your-key) · [Expiry and revocation](#expiry-and-revocation)

## Create a key

1. Sign in to EffortSignal and open **Settings → Integrations**. In the Russian interface, this is **Настройки → Подключения**.
2. Find **Personal API keys** and select **Create API key**.
3. Enter a **Key name** that identifies the script, such as `Local training export`.
4. Choose **Expires after**: 30, 90, 180 or 365 days. The default is 90 days.
5. Select at least one permission from the list below. For reading data, select **Read workouts, thresholds and daily health data**. For an uploader, select only the categories it sends.
6. Select **Create API key**, then **Copy key**. Save it in your script's secret settings or a password manager before selecting **Done**.

The complete key starts with `es_personal_` and is shown only once. The list in Settings keeps its name, permissions, expiry and last-used date. If you lose the secret, create a replacement and revoke the old key.

## Choose permissions

The creation dialog describes each permission in plain language. These are the corresponding API scope names:

| Permission | What the key can do |
| --- | --- |
| `training:read` | Read saved workouts and their available measurements, daily health records, current thresholds, units, timezone and training preferences |
| `activities:write` | Upload completed workouts and their recorded data |
| `sleep:write` | Send main sleep, deep sleep and confirmed naps |
| `health:write` | Send daily health measurements, including HRV and resting heart rate |

These permissions apply to the account that created the key. They do not allow editing FTP or other profile settings, scheduling workouts, deleting training history or accessing another athlete's account.

Workout measurements received through the Garmin or Strava APIs cannot be read with personal keys. Such workouts can appear in a list with a restriction message instead of measurements. Recordings with unknown provenance may also be unavailable. Check `details_access.available` and its explanation before using a workout's data; an unavailable value is not zero.

## Use your key

Send the complete key in the `Authorization` header over HTTPS:

```text
Authorization: Bearer YOUR_PERSONAL_API_KEY
```

The API base URL is `https://app.effortsignal.com/api/v1/ingest/`. Keep the key in private secret storage. Do not place it in a URL, public repository, browser application or shared log. It authenticates this API only; it does not sign in to the web application, connect an MCP client or create other keys.

This Python 3 example makes a read-only request for your current training settings. It uses the standard library and asks for the key at a hidden prompt. Create a key with `training:read`, save the code in a local `.py` file and run it with `python3`. The response contains your account data, so run it in a private terminal.

```python
import getpass
import json
from urllib.request import HTTPRedirectHandler, Request, build_opener


class NoRedirect(HTTPRedirectHandler):
    # Refuse redirects so the Authorization header cannot reach a different host.
    def redirect_request(self, request, file, code, message, headers, new_url):
        return None


# A hidden prompt keeps the secret out of the script and shell command history.
key = getpass.getpass("Personal API key: ").strip()
request = Request(
    "https://app.effortsignal.com/api/v1/ingest/user-context/",
    headers={"Authorization": f"Bearer {key}", "Accept": "application/json"},
)
with build_opener(NoRedirect()).open(request, timeout=30) as response:
    result = json.load(response)
print(json.dumps(result, indent=2, ensure_ascii=False))
```

## Read saved data

With `training:read`, use these routes relative to the API base URL:

| Request | Result |
| --- | --- |
| `GET activities/` | A page of saved completed workouts, including access restrictions |
| `GET activities/{activity_id}/` | One workout, its available saved metrics and requested detail sections |
| `GET daily-health/` | Saved daily measurements with their local dates and sources |
| `GET user-context/` | Current thresholds, units, timezone and training preferences |

Activity and daily-health lists default to the last 30 local days. Set inclusive `date_from` and `date_to` for a period of up to 366 days. `limit` accepts 1–100 and defaults to 20. Follow `data.next_cursor` while `data.has_more` is true, keeping the same query parameters. Activity lists also accept `sport` and `sort=newest` or `sort=oldest`.

Read responses contain `schema_version`, `generated_at`, `data` and `warnings`. Daily measurements are returned per saved source, so one date can have several records. Reads use saved data; they do not request fresh data from your devices or providers. See [OpenAPI](https://app.effortsignal.com/api/schema/) for fields and workout-detail sections.

## Send recordings and measurements

With the matching write permission, send a completed recording to `POST activities/`, or sleep and daily measurements to `POST daily-health/`. `GET connection/` returns the key's connection, permissions, profile timezone, supported formats and upload limits.

Use the [recording and daily-measurement formats](/developers/#send-data) from API for Companies, supplying your personal key as the bearer token. The source IDs, revision timestamps, timezone and provenance rules are the same. Start with a test account if your script sends artificial data.

An activity upload returns an import receipt. Save `import_job_id` and read `GET imports/{import_job_id}/`: acceptance is separate from publication, and `published=true` confirms the activity is available. Retry an upload with the same source ID, revision and bytes to avoid creating another import.

Each key has its own upload source and receipts. A replacement key cannot read the previous key's import receipts. Keep the original key's receipts in your script when replacing a key; expiry and revocation retain accepted training and daily history.

## Expiry and revocation

To stop a script's access, return to **Settings → Integrations → Personal API keys**, find its name and select **Revoke key**. Revocation takes effect immediately. Other keys and imported history remain available.

An expired or revoked key returns `401`. Create a replacement in Settings and update the script's secret. Personal keys have no refresh-token exchange. A `403` means the key lacks the permission required by that route; create a key with the permissions your script needs. A `400` means the request needs correction; inspect the response before retrying.

If a key is exposed, revoke it and replace it. For support, contact **hello@effortsignal.com** with the endpoint, HTTP status and timestamp. Leave the key, authorization header and private account data out of the message.
