This article describes how to register an application with ActivityInfo so that ActivityInfo users can grant it access to their accounts, and how to add the OAuth flow to your application with a standard OAuth library.
Register an application when other people sign in to it with their own ActivityInfo accounts. If you only need to reach your own data from a script or a reporting tool, use a Personal API Token instead.
ActivityInfo acts as an OAuth 2.1 authorization server and supports the authorization code flow with PKCE. Registration is open: you register your application over HTTP, no ActivityInfo account or approval is needed, and you receive a client_id in return. ActivityInfo issues no client secret. Every registered application is a public client that proves its identity with PKCE, so the same registration works for a web application, a desktop application, a mobile application, and a command-line tool.
Before you begin
Decide on the redirect URI your application uses. This is the address ActivityInfo sends the browser back to, with the authorization code, once the user approves your application. ActivityInfo accepts three forms:
https://your-app.example.org/callbackfor an application hosted on the web. Plainhttp://is rejected for any host other than loopback, so an authorization code never travels unencrypted.http://127.0.0.1:8721/callback,http://[::1]:8721/callbackorhttp://localhost:8721/callbackfor a desktop application or command-line tool that listens on a local port. Any port number is accepted.com.example.myapp://oauth/callbackfor a mobile or desktop application that registers its own URL scheme with the operating system. The scheme must contain at least one dot.
The redirect URI must not contain a fragment (#...) or user information (user:password@).
Step 1: Read the OAuth endpoints
ActivityInfo publishes its endpoints as an OAuth 2.0 Authorization Server Metadata document (RFC 8414). Read the document rather than hard-coding the endpoints, so that your application also works against a self-managed ActivityInfo server on its own domain.
curl https://www.activityinfo.org/.well-known/oauth-authorization-server
The response includes the values your OAuth library needs:
{
"issuer": "https://www.activityinfo.org",
"authorization_endpoint": "https://www.activityinfo.org/oauth/authorize",
"token_endpoint": "https://www.activityinfo.org/oauth/token",
"registration_endpoint": "https://www.activityinfo.org/oauth/register",
"scopes_supported": ["activityinfo.read", "activityinfo.write"],
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"code_challenge_methods_supported": ["S256"],
"token_endpoint_auth_methods_supported": ["none"]
}
Most OAuth libraries read this document for you when you give them the issuer URL.
Step 2: Register your application
Send a POST request to the registration endpoint with the name of your application and its redirect URIs. The request needs no authentication.
curl -X POST https://www.activityinfo.org/oauth/register \
-H "Content-Type: application/json" \
-d '{
"client_name": "Field Report Sync",
"redirect_uris": ["https://your-app.example.org/callback"]
}'
| Field | Description |
|---|---|
client_name |
The name shown to users on the consent screen. Up to 200 characters. |
redirect_uris |
One to five redirect URIs, each up to 2048 characters. |
ActivityInfo replies with status 201 and the registration:
{
"client_id": "sQ8vK2mR7pX4nB9dL6tY3wF1jH5gC0aZ",
"client_id_issued_at": 1767225600,
"client_name": "Field Report Sync",
"redirect_uris": ["https://your-app.example.org/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "none"
}
Step 3: Store the client_id
Store the client_id in your application's configuration and reuse it for every user. Register once per application, not once per user.
The client_id is not a secret, and the response contains no client secret: token_endpoint_auth_method is none. Instead of a secret, each authorization request carries a PKCE code challenge, and ActivityInfo only releases the access token to the party that can produce the matching code verifier.
A registration that no user has ever approved may be removed after 30 days. If your client_id stops being recognised before you have any users, register again.
Step 4: Add the flow to your application
You do not need to implement the flow by hand. ActivityInfo follows OAuth 2.1, so any maintained OAuth client library handles it once you supply four values:
- the authorization endpoint and the token endpoint from Step 1,
- your
client_idfrom Step 2, - one of your registered redirect URIs, and
- the scope you are asking for:
activityinfo.read, oractivityinfo.writeif your application needs to make changes.
Two requirements shape which library you can use. The library must support PKCE with the S256 method, which ActivityInfo requires, and it must support public clients, because there is no client secret to send. These libraries meet both:
| Language | Library |
|---|---|
| Python | Authlib, requests-oauthlib |
| Node.js | openid-client |
| Browser JavaScript | oauth4webapi |
| Java, Kotlin | Nimbus OAuth 2.0 SDK, Spring Security OAuth2 Client |
| .NET | IdentityModel.OidcClient |
| Go | golang.org/x/oauth2 |
| PHP | league/oauth2-client |
| Ruby | oauth2 |
| R | httr2 |
Some libraries enable PKCE by default and others take it as an option, so check that S256 is switched on. Set the token endpoint authentication method to none where the library asks for one.
The following example uses Authlib in Python. The equivalent in another library is the same three calls: build the authorization URL, receive the code on your redirect URI, then exchange the code together with the code verifier.
from authlib.common.security import generate_token
from authlib.integrations.requests_client import OAuth2Session
AUTHORIZE_URL = "https://www.activityinfo.org/oauth/authorize"
TOKEN_URL = "https://www.activityinfo.org/oauth/token"
session = OAuth2Session(
client_id="sQ8vK2mR7pX4nB9dL6tY3wF1jH5gC0aZ",
redirect_uri="http://127.0.0.1:8721/callback",
scope="activityinfo.write",
code_challenge_method="S256",
token_endpoint_auth_method="none",
)
# 1. Send the user to this URL and keep the verifier and the state.
code_verifier = generate_token(48)
authorization_url, state = session.create_authorization_url(
AUTHORIZE_URL, code_verifier=code_verifier
)
# 2. Your redirect URI receives ?code=...&state=... Check that the state matches.
# 3. Exchange the code for tokens.
token = session.fetch_token(TOKEN_URL, code=code, code_verifier=code_verifier)
Send the access token from the token response as a bearer token on every API request, as described in Authenticate:
Authorization: Bearer <ACCESS TOKEN>
Refreshing the access token
An access token expires one hour after ActivityInfo issues it. The token response also contains a refresh token, which your application exchanges for a new access token without involving the user. Send the client_id with the refresh request:
curl -X POST https://www.activityinfo.org/oauth/token \
-d grant_type=refresh_token \
-d refresh_token=<REFRESH TOKEN> \
-d client_id=<CLIENT ID>
ActivityInfo returns a new access token and a new refresh token, and the refresh token you sent stops working. Store the new refresh token in place of the old one. Presenting a refresh token that has already been exchanged is treated as a sign that the token was stolen: ActivityInfo revokes the user's authorization, and the user has to approve your application again.
What your users see
The first time a user opens your authorization URL, ActivityInfo asks them to sign in, then shows the “Authorize application” screen. The screen leads with the destination that receives the authorization code, because ActivityInfo verifies that destination but cannot verify a name that anyone may register. Your client_name appears below it, marked as unverified.
The user then chooses what to grant. Read access is always part of the request. If you ask for activityinfo.write, the user sees an additional checkbox for making changes, which they can clear to grant read access only. So always check the scope in the token response, and handle a token that came back with only activityinfo.read. Ask for the scope your application needs: if you send no scope at all, ActivityInfo asks the user for write access.
The approved application appears on the “Authorized Applications” page of the user's account settings, with the scope they granted and the date it was last used. The user can revoke it from that page at any time. After a revocation your access token and refresh token both stop working, and your application has to send the user through the flow again.
Reference
| Setting | Value |
|---|---|
| Authorization endpoint | https://www.activityinfo.org/oauth/authorize |
| Token endpoint | https://www.activityinfo.org/oauth/token |
| Registration endpoint | https://www.activityinfo.org/oauth/register |
| Grant types | authorization_code, refresh_token |
| PKCE | Required, method S256 |
| Client authentication | None. Public clients only |
| Scopes | activityinfo.read, activityinfo.write |
| Authorization code lifetime | 2 minutes, single use |
| Access token lifetime | 1 hour |
| Refresh token lifetime | 30 days, rotated on every use |
| Redirect URIs per application | 5 |
| Registrations per hour per network address | 20 |
Troubleshooting
| Error | Cause |
|---|---|
invalid_redirect_uri from /oauth/register |
The redirect URI uses a scheme other than https://, http:// to loopback, or a reverse-DNS scheme; or it contains a fragment or user information. |
invalid_client: unknown client_id from /oauth/authorize |
The client_id was never registered, or its registration was removed because no user had approved it. Register again. |
redirect_uri does not match a registered URI |
The redirect URI in the authorization request differs from the registered value. ActivityInfo compares the whole URI exactly, including the port and the path. |
PKCE with code_challenge_method=S256 is required |
The authorization request sent no code_challenge, or used the plain method. Switch PKCE on in your library. |
invalid_grant: PKCE verification failed |
The code verifier sent to the token endpoint does not match the code challenge sent to the authorization endpoint. Keep the verifier for the whole flow, one per authorization request. |
invalid_grant: Unknown, expired or already-used code |
The authorization code is more than 2 minutes old, or it was already exchanged. Codes are single use, even when the exchange fails. |
invalid_request: Missing client_id from a refresh request |
Add client_id to the refresh token request. |
401 Unauthorized from the API |
The access token expired after an hour, or the user revoked your application. Refresh the token, and send the user through the flow again if the refresh also fails. |