Developer controls
Manage Applications with your signed-in Strave identity.
The /developer/* routes are the control plane for Applications, versions,
Installations, client secrets, webhooks, and budgets. They use the
permissions of a signed-in Strave user. An Application OAuth token, including a
client-credentials token, cannot manage its own control-plane resources.
Owners and permissions
An organisation owns each Application and pays for its usage. Users and teams cannot own new Applications. The owner organisation controls access with two role permissions:
- Developer applications: View shows Applications, Installations, usage, and budgets.
- Developer applications: Manage also lets a member create and change Applications, reset secrets, publish versions, change budgets, transfer ownership, and approve Installations on the organisation or its events.
Organisation owners and admins have both permissions. Event managers can view Applications. Give other roles access in the organisation's role settings.
Create an Application in the organisation dashboard
The organisation dashboard is the easiest way to manage Applications. Open the
dashboard of the organisation and select Developer applications in the
Manage section of the sidebar. The page is at
strave.gg/o/<organisation-slug>/manage/applications.
- Select New application. You need the
developerApp:managepermission. - Enter a name and, if you want, a description.
- Keep the type General application. It is the only type today.
- Select Create application and copy the client secret. Strave shows the secret only once.
The new Application belongs to the organisation of the dashboard.
The Application then has these sections:
| Section | What you do there |
|---|---|
| General information | Change the name, description, icon, and homepage. Reset the secret. |
| OAuth2 | Edit redirect URIs and grant types. |
| Permissions | Select OAuth scopes. Paid scopes show the plan that the owner needs. |
| Installations | See where the Application is installed. Approve or revoke access. |
| Usage and budget | See this month's usage and set limits, warnings, and the kill switch. |
| Ownership | Transfer the Application to another organisation. |
| Danger zone | Archive the Application. |
Redirect URIs, grant types, and permissions belong to an Application version. Save your changes to a draft, select Validate, and then select Publish. Publishing adds the draft values to the live OAuth client. Values from earlier published versions stay active.
An owner transfer stays pending for 7 days. A member who can manage developer applications in the receiving organisation accepts it on the Developer applications page of that organisation.
Authenticate a control request
Strave accepts either of these session-backed credentials:
- A Strave session cookie. This is intended for a trusted same-site server or proxy that already holds the user's session.
Authorization: Bearer <token>with the short-lived Convex identity token from the signed-in session. First-party browser clients obtain it with the Better Auth Convex client after session recovery succeeds.
const response = await authClient.convex.token({
fetchOptions: { throw: false },
});
const controlToken = response.data?.token;Do not confuse this value with STRAVE_ACCESS_TOKEN, which is an OAuth token
for an installed Application. Keep control tokens out of URLs, logs, source
control, and persistent browser storage.
Create an Application with the API
Creation is idempotent. Generate a unique key for the intended operation and
reuse that key only when retrying the same request body. The owner must be an
organisation, and kind must be general. Profile URLs must use HTTPS.
curl --request POST https://strave.gg/api/v2/developer/apps \
--header "Authorization: Bearer $STRAVE_CONTROL_TOKEN" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: app-create-acme-production" \
--data '{
"owner": { "type": "organisation", "id": "org_01K4ZK3J1P" },
"kind": "general",
"name": "Acme Tournament Sync",
"description": "Synchronizes brackets and broadcast state.",
"homepageUrl": "https://integrations.example.com/strave"
}'A successful response is 201 Created. Save clientSecret immediately because
Strave returns its plaintext value only when the secret is created or rotated.
{
"data": {
"application": {
"id": "app_01K4ZKC87A",
"owner": { "type": "organisation", "id": "org_01K4ZK3J1P" },
"clientId": "strave_app_01K4ZKC87A",
"kind": "general",
"name": "Acme Tournament Sync",
"description": "Synchronizes brackets and broadcast state.",
"homepageUrl": "https://integrations.example.com/strave",
"iconUrl": null,
"status": "active",
"revision": 1,
"createdAt": "2026-09-15T09:30:00.000Z",
"updatedAt": "2026-09-15T09:30:00.000Z"
},
"clientSecret": "strave_secret_example"
}
}The response includes ETag: "1" and Idempotency-Replayed: false.
Update without overwriting another change
Read the Application, keep its ETag, and send that value in If-Match:
curl --request PATCH https://strave.gg/api/v2/developer/apps/app_01K4ZKC87A \
--header "Authorization: Bearer $STRAVE_CONTROL_TOKEN" \
--header 'If-Match: "1"' \
--header "Content-Type: application/json" \
--data '{ "name": "Acme Broadcast Sync" }'A 412 revision_mismatch means the resource changed after your read. Fetch it
again, reconcile the changes, and retry with the new ETag. A 428 revision_required means If-Match was missing or was not a strong revision
tag.
Common failures
| Status | Code | What to do |
|---|---|---|
401 | authentication_required | Sign in again and obtain a fresh control token. |
403 | permission_denied | Use a member with the Manage developer applications permission. |
409 | idempotency_key_conflict | Use the original body or generate a new key for a new operation. |
412 | revision_mismatch | Re-read, reconcile, and retry with the current ETag. |
428 | revision_required | Send the strong ETag in If-Match. |
Every response includes x-request-id. Record it with failures, but redact
authorization, cookies, client secrets, and webhook secrets.