OAuth apps
An OAuth app is software you register once and that any Essentio user can then connect to one of their Businesses. Each connection — a Connected app — acts for the person who made it, in the one Business they chose, with a Role no higher than theirs.
Use an API key instead when the program is the Business's own: a key belongs to the Business and keeps working when people come and go. A Connected app belongs to the person who connected it and ends when they leave the Business.
Register your app
In Essentio, open Profile → Apps and choose Register OAuth App. Give it:
- a name, which the consent screen shows to everyone who connects it;
- its redirect addresses, one or more: where the consent screen sends the person back. Each is an
https://address with no#fragment;http://is accepted only onlocalhost,127.0.0.1or[::1], for a desktop or command-line app listening on the person's own machine; - whether it keeps a secret. An app that runs on a server keeps one; a desktop, mobile or browser app cannot, and proves itself with PKCE alone.
There is no review: the app can be connected as soon as it is registered. You accept the OAuth app terms when you register it. Essentio shows the App ID, which you send as client_id, and — for an app that keeps a secret — the secret, sent as client_secret, once. If the secret is lost or leaks, give the app a new secret from the same page; the old one stops working at once.
Send the person to the consent screen
Every app, with a secret or without, uses the authorization code grant with PKCE and the S256 method. Make a random code_verifier of 43 to 128 characters for each attempt, keep it, and send its SHA-256, base64url-encoded without padding, as the code_challenge:
The address of the consent screen
https://essentio.pro/oauth/authorize
?response_type=code
&client_id=YOUR_APP_ID
&redirect_uri=https://yourapp.example/callback
&state=A_RANDOM_VALUE_YOU_CHECK_ON_RETURN
&code_challenge=BASE64URL_SHA256_OF_THE_VERIFIER
&code_challenge_method=S256
&resource=https://api.essentio.pro
In code — keep the verifier and the state with the person’s session, and send them to the address:
Make the verifier, the challenge and the consent address
verifier=$(openssl rand -base64 48 | tr '+/' '-_' | tr -d '=\n')
challenge=$(printf '%s' "$verifier" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')
state=$(openssl rand -hex 16)
echo "https://essentio.pro/oauth/authorize?response_type=code&client_id=$ESSENTIO_APP_ID\
&redirect_uri=https%3A%2F%2Fyourapp.example%2Fcallback&state=$state\
&code_challenge=$challenge&code_challenge_method=S256&resource=https%3A%2F%2Fapi.essentio.pro"
resource names what the token is for (RFC 8707): https://api.essentio.pro, the Public API — also what a request without resource gets — or https://mcp.essentio.pro, Essentio's MCP server. A token is bound to the one it was asked for, through every refresh: the other answers 401. A resource Essentio does not serve, or two of them, is sent back as error=invalid_target. At the token endpoint resource may be sent again; it must name the same one, or the answer is invalid_target and the tokens just issued are revoked.
The person signs in to Essentio if they are not, and sees the consent screen. It names your app, says Essentio has not verified it, and lets them pick one of their Businesses and a Role no higher than theirs there — Admin, Member or Viewer, never Owner. There are no scopes: what your app may do is what that Role may do.
If they connect it, Essentio sends them to your redirect_uri with code and your state. If they cancel, it sends error=access_denied. A request without an S256 challenge — plain, or none — is sent back with error=invalid_request before anything is shown.
Every answer sent to your redirect_uri, a code or an error, also carries iss=https://essentio.pro, the issuer (RFC 9207). Check it is exactly that before you use the code or show the error: an app that talks to several authorization servers cannot then be handed one's answer as another's. A request whose client_id or redirect_uri Essentio does not know is never redirected: it is answered on the page.
Exchange the code
Within ten minutes, check the state and iss that came back, then trade the code and the verifier for tokens, from your server:
Trade the code for tokens
curl https://essentio.pro/oauth/token \
-d grant_type=authorization_code \
-d client_id="$ESSENTIO_APP_ID" \
-d client_secret="$ESSENTIO_APP_SECRET" \
-d redirect_uri=https://yourapp.example/callback \
-d code_verifier="$VERIFIER" \
-d code="$CODE"
An app with no secret leaves out client_secret. The answer:
200
{
"token_type": "Bearer",
"expires_in": 3600,
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9…",
"refresh_token": "def50200…"
}
Call the API
Send the access token exactly as an API key is sent:
A request with an access token
curl https://api.essentio.pro/v1/clients \
-H "Authorization: Bearer ACCESS_TOKEN"
Everything else is the same as with a key: the errors, the rate limit — each Connected app has its own 120 requests a minute — and the request log the Business's Owner and Admins read, where your requests are named by your app and the person who connected it.
Refresh the token
An access token lasts an hour. Before it ends, exchange the refresh token for a new pair:
Refreshing
curl https://essentio.pro/oauth/token \
-d grant_type=refresh_token \
-d client_id=YOUR_APP_ID \
-d client_secret=YOUR_SECRET \
-d refresh_token=THE_REFRESH_TOKEN
A refresh token is exchanged once: the answer carries a new one, and the old one is refused from then on. Keep the newest.
Presenting a refresh token that was already exchanged is taken as a stolen token: every token of that Connected app is revoked, and the person has to connect the app again. There is no grace window. A refresh retried after a timeout — when the first one reached Essentio and only its answer was lost — counts as a reuse, and so do two refreshes of the same token sent at once: Essentio does not queue them. Refresh from one place, one at a time, and store the new token before using it.
Discovery
A program can find all of this by itself:
https://essentio.pro/.well-known/oauth-authorization-serveris the authorization server's metadata (RFC 8414): its endpoints, the one grant and its refresh,S256, public clients (none) beside secrets, Client ID Metadata Documents, the registration endpoint andiss. No scopes are listed: there are none.https://api.essentio.pro/.well-known/oauth-protected-resourceandhttps://mcp.essentio.pro/.well-known/oauth-protected-resourceare the Public API's and the MCP server's protected resource metadata (RFC 9728): each one'sresourceand its authorization server. A401from either names its own inWWW-Authenticate: Bearer resource_metadata="…".
Protected resource metadata
Each protected resource answers this on its own host, with no key or token: https://api.essentio.pro/.well-known/oauth-protected-resource for the Public API and https://mcp.essentio.pro/.well-known/oauth-protected-resource for the MCP server. The answer (RFC 9728) names the resource — its origin, the resource your app asks for — and its authorization server, https://essentio.pro, whose own metadata is at /.well-known/oauth-authorization-server.
AI assistants
Claude, ChatGPT and other AI assistants connect with no registration by anyone. They are OAuth apps too, but they register themselves, and they may do less than an app a person registered:
- By a Client ID Metadata Document, which Claude and ChatGPT use: the assistant sends an
https://URL as itsclient_id, and Essentio fetches the JSON document there when the consent screen opens. The document must name its own URL asclient_id, carry aclient_nameandredirect_uris, and ask for no secret —token_endpoint_auth_methodnone, or another withnoneamong itstoken_endpoint_auth_methods_supported. If it listsgrant_types, they must includeauthorization_code(andresponse_typesmust includecode); any other grant it lists is ignored, since Essentio serves only the authorization code and its refresh. It must be served asapplication/json. It is fetched over HTTPS only, from an ASCII host name (no IP address) that resolves only to public addresses, with no redirect followed, no more than 5 KB read and 4 seconds for the transfer, and kept as long as itsCache-Controlsays (between five minutes and a day); one that fails is not kept. The consent screen shows the host the document is on. - By Dynamic Client Registration (RFC 7591), for an assistant that does not use a document: it posts its
client_nameandredirect_urisas JSON tohttps://essentio.pro/oauth/registerand gets aclient_id— with the same rule forgrant_typesandresponse_types, and an answer that states the grants it was registered with, ten registrations an hour from one address. One that nobody connects is deleted a day later.
Either way the app is public: no secret, PKCE alone (none at the token endpoint). Its redirect addresses may only be Claude's callback (https://claude.ai/api/mcp/auth_callback, and https://claude.com/api/mcp/auth_callback), ChatGPT's (https://chatgpt.com/connector_platform_oauth_redirect), or an http:// loopback address — localhost, 127.0.0.1 or [::1], matched on any port, where the consent screen warns that any program on that computer could be listening. Its name may not read as Essentio. And it reaches the MCP server only: its resource is https://mcp.essentio.pro, also when it sends none, and asking for the Public API is invalid_target. To call the Public API, register an OAuth app in Profile → Apps. What an assistant can read through the MCP server, and how a person connects one, is on AI assistants (MCP).
What a Connected app may do, and when it ends
A Connected app never does more than the person who connected it may do now. On every request it acts with the lower of the Role it was given and that person's Role in the Business at that moment: if an Owner lowers their Role, your app is cut back on its next request.
It ends — its token is answered 401, its refresh token invalid_grant — when:
- the person disconnects it, from their Profile, or an Owner or Admin of the Business revokes it, from Settings → API Keys;
- the person leaves the Business or deletes their account. Joining again does not bring it back;
- you delete your OAuth app, which ends every Connected app of it;
- it is unused for 90 days: no request to the API was made with it, and its app’s Webhook took no message about that Business with a
2xx, for 90 days (a refresh is not a use), so it refreshes no more.
To act again, send the person through the consent screen again.
OAuth app terms
You accept these when you register an OAuth app:
- Your app acts only as the person who connected it allowed: in the one business they chose, with the role they gave it.
- You use a business's data only to do what your app does for that business, and never sell it or pass it on.
- You keep your app's secret and its tokens private, and if one leaks you give the app a new secret or delete it.
- Essentio does not review or verify OAuth apps, and may disable one that breaks these terms.