OAuth apps
Build integrations with Hackatime using OAuth 2.0. Create an OAuth app to let users authorize your application to access their Hackatime data.
Creating an OAuth app
- Sign in to your Hackatime account
- Go to My OAuth Apps
- Click New Application
- Fill in the form:
- Name - a human-readable name for your app (shown on the consent screen)
- Redirect URIs - one URI per line where users are sent after authorizing (e.g.
https://sinerider.com/auth/callback) - Scopes - the permissions your app needs (see Scopes below)
- Confidential - check this if your app can keep a client secret safe (server-side apps). Leave unchecked for native/mobile/SPA apps.
- Click Submit
After creation you will see your Client ID (UID) and Client Secret!
Scopes
Scopes control what data your app can access. It’s a good idea to only request the scopes you need!
| Scope | Description | Granted by Default |
|---|---|---|
profile |
Access basic profile information (user ID, email addresses, Slack ID, GitHub username, trust factor) | Yes |
read |
View basic info about the user’s Hackatime account | No |
admin |
Access the Admin API on the authorizing user’s behalf, subject to their role permissions. | No |
If you don’t specify any scopes, only the profile scope is granted.
When requesting scopes in the authorization URL, separate multiple scopes with spaces:
scope=profile+read
Admin scope restrictions
- Only admin+ users (
admin,superadmin,ultraadmin) can attach theadminscope to an OAuth application. - Viewers and admin+ users (
viewer,admin,superadmin,ultraadmin) can authorize an approved application requesting theadminscope. The token does not grant permissions beyond the authorizing user’s role. - Apps with the
adminscope must be confidential (server-side clients that can keep a secret). - This scope requires approval from Hack Club HQ to use. It won’t work if you try to use it without permission!
Authorization flow
Hackatime supports the standard Authorization Code flow. PKCE (Proof Key for Code Exchange) is also supported for public clients.
Step 1: Redirect users to authorize endpoint
Send users to the authorization endpoint:
GET https://hackatime.hackclub.com/oauth/authorize
Parameters:
| Parameter | Required | Description |
|---|---|---|
client_id |
Yes | Your app’s UID |
redirect_uri |
Yes | Must match one of your registered redirect URIs |
response_type |
Yes | Set to code |
scope |
No | Space-separated list of scopes (defaults to profile) |
state |
Recommended | A random string to prevent CSRF attacks. You should verify this matches when the user is redirected back. |
code_challenge |
For PKCE | The code challenge for PKCE |
code_challenge_method |
For PKCE | The method used to generate the challenge (e.g. S256) |
Example:
https://hackatime.hackclub.com/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=https://sinerider.com/auth/callback&response_type=code&scope=profile+read&state=random_string
Step 2: Handle the callback
After the user authorizes (or denies), they are redirected to your redirect_uri with a code parameter:
https://sinerider.com/auth/callback?code=AUTHORIZATION_CODE&state=random_string
If the user denies authorization, the callback includes an error parameter instead.
Step 3: Exchange the code for a token
Make a POST request to the token endpoint to exchange the authorization code for an access token:
POST https://hackatime.hackclub.com/oauth/token
Parameters (form-encoded body):
| Parameter | Required | Description |
|---|---|---|
client_id |
Yes | Your app’s UID |
client_secret |
Yes (confidential apps) | Your app’s secret |
code |
Yes | The authorization code from the callback |
redirect_uri |
Yes | Must match the URI used in the authorization request |
grant_type |
Yes | Set to authorization_code |
code_verifier |
For PKCE | The original code verifier if you used PKCE |
Example response:
{
"access_token": "abc123...",
"token_type": "Bearer",
"expires_in": 504911232,
"scope": "profile read",
"created_at": 1700000000
}
Access tokens are long-lived (approximately 16 years) so you typically don’t need to worry about refreshing them. Unless, of course, you want your Hackatime app to keep working after 16 years…
Step 4: Make API requests
Use the access token in the Authorization header:
GET https://hackatime.hackclub.com/api/v1/authenticated/me
Authorization: Bearer YOUR_ACCESS_TOKEN
Admin API access
With a token that includes the admin scope, call Admin API endpoints the same way (instead of minting an Admin API key):
GET https://hackatime.hackclub.com/api/admin/v1/check
Authorization: Bearer YOUR_ACCESS_TOKEN
OAuth-authenticated API endpoints
All endpoints below require a valid OAuth access token in the Authorization: Bearer <token> header.
GET /api/v1/authenticated/me
Required scope: profile
Returns information about the authenticated user.
Response:
{
"id": 123,
"emails": ["[email protected]"],
"slack_id": "U059VC0UDEU",
"github_username": "liamcal",
"trust_factor": {
"trust_level": "green",
"trust_value": 2
}
}
GET /api/v1/authenticated/hours
Required scope: read
Returns total coding time for a date range.
Query parameters:
| Parameter | Default | Description |
|---|---|---|
start_date |
7 days ago | Start date (YYYY-MM-DD) |
end_date |
Today | End date (YYYY-MM-DD) |
Response:
{
"start_date": "2025-01-01",
"end_date": "2025-01-07",
"total_seconds": 36000
}
GET /api/v1/authenticated/streak
Required scope: read
Returns the user’s current coding streak.
Response:
{
"streak_days": 14
}
GET /api/v1/authenticated/projects
Required scope: read
Returns the user’s projects with time totals.
Query parameters:
| Parameter | Default | Description |
|---|---|---|
include_archived |
false |
Set to true to include archived projects |
Response:
{
"projects": [
{
"name": "my-project",
"total_seconds": 72000,
"most_recent_heartbeat": "2025-01-07T15:30:00Z",
"languages": ["Ruby", "JavaScript"],
"archived": false
}
]
}
GET /api/v1/authenticated/heartbeats/latest
Required scope: read
Returns the user’s most recent heartbeat.
Response:
{
"id": 456,
"created_at": "2025-01-07T15:30:00Z",
"time": 1736264400.0,
"category": "coding",
"project": "my-project",
"language": "Ruby",
"editor": "VS Code",
"operating_system": "Mac",
"machine": "MacBook-Pro",
"entity": "app/models/user.rb"
}
If the user has no heartbeats (excluding setup test entries), the endpoint returns 200 OK with:
{
"heartbeat": null
}
GET /api/v1/authenticated/api_keys
This endpoint does not require a specific OAuth scope beyond a valid access token.
Returns the user’s Hackatime API key (creates one if none exists).
Response:
{
"token": "abc123..."
}
Revoking access
As a user
Users can revoke access to your app at any time:
- Go to Privacy & Security
- Click Revoke next to the app
Programmatically
You can revoke a token by calling the revoke endpoint:
POST https://hackatime.hackclub.com/oauth/revoke
Parameters:
| Parameter | Required | Description |
|---|---|---|
token |
Yes | The access token to revoke |
client_id |
Yes | Your app’s UID |
client_secret |
Yes (confidential apps) | Your app’s secret |
PKCE for public clients
If your app cannot securely store a client secret (mobile apps, desktop apps, SPAs), use PKCE:
- Generate a random
code_verifier(43-128 characters, URL-safe) - Create a
code_challengeby computingBASE64URL(SHA256(code_verifier)) - Include
code_challengeandcode_challenge_method=S256in the authorization URL - Include
code_verifierwhen exchanging the code for a token
You can also leave the Confidential checkbox unchecked and omit the client_secret in your token requests.
App verification
New OAuth apps are marked as unverified. Unverified apps trigger a warning on the consent screen telling users the app has not been reviewed. To get your app verified, shoot Mahad a DM on the Slack! Verified apps:
- Don’t show an “unverified” warning during authorization
- Have their name locked to prevent impersonation (only admins can rename them)
Need help?
- Check the interactive API docs for full endpoint details.
- Ask in #hackatime-help on Slack.
- Open an issue on GitHub.