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.

  1. Select New application. You need the developerApp:manage permission.
  2. Enter a name and, if you want, a description.
  3. Keep the type General application. It is the only type today.
  4. 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:

SectionWhat you do there
General informationChange the name, description, icon, and homepage. Reset the secret.
OAuth2Edit redirect URIs and grant types.
PermissionsSelect OAuth scopes. Paid scopes show the plan that the owner needs.
InstallationsSee where the Application is installed. Approve or revoke access.
Usage and budgetSee this month's usage and set limits, warnings, and the kill switch.
OwnershipTransfer the Application to another organisation.
Danger zoneArchive 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

StatusCodeWhat to do
401authentication_requiredSign in again and obtain a fresh control token.
403permission_deniedUse a member with the Manage developer applications permission.
409idempotency_key_conflictUse the original body or generate a new key for a new operation.
412revision_mismatchRe-read, reconcile, and retry with the current ETag.
428revision_requiredSend 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.