Developer console
Монгол
·
English
Connecting your own system to Gerege Nexus. The platform is itself an OAuth2 / OpenID Connect provider: your application signs a person in here, then calls the API on their behalf.
1. Register a client
Install the Developer module in your workspace and
open /developer/apps. Press New app and
supply:
| Field | Meaning |
|---|---|
| Name | Shown to the person on the consent screen |
redirect_uris |
Required. At least one. Absolute, no fragment
(#). Everything except loopback
(http://127.0.0.1:…) must be HTTPS |
| Public client | Yes for a browser or mobile app — no secret, proved by PKCE |
Registration returns a client_id. The
client_secret appears in that one response
only — there is no API that reads it back. If you lose it, or
it leaks, you do not need to register a new client: use the
rotate action in §6, Managing a
client, below.
Why: an API that hands the secret back would be the easiest way to steal it. Showing it once and keeping only a digest means that even somebody who reads the database cannot recover it.
2. The sign-in flow
Standard authorization code + PKCE. No other grant is offered — not implicit, not password.
2.1 Prepare a verifier
code_verifier = a random string of 43–128 characters
code_challenge = BASE64URL( SHA256( code_verifier ) )
code_challenge_method must be
S256. plain is refused, since
it would make PKCE pointless.
2.2 Send the person
GET https://cloud.gerege.mn/openerp/oauth2/auth
?response_type=code
&client_id=<your client_id>
&redirect_uri=<exactly as registered>
&scope=openid profile email
&state=<random, against CSRF>
&code_challenge=<from above>
&code_challenge_method=S256
The platform reads its own session:
- not signed in → the sign-in screen;
- no workspace chosen → the chooser;
- consent missing → the consent screen;
- everything settled → back to
redirect_uri?code=…&state=…
redirect_uri must match what you registered
exactly — no prefix matching, no ignoring the query
string. An error found before the client and redirect were
verified is never bounced to that address; the platform reports it on
its own page, which is what stops it being an open redirect.
2.3 Exchange the code
POST https://cloud.gerege.mn/openerp/oauth2/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=<the returned code>
&redirect_uri=<the same address>
&client_id=<client_id>
&code_verifier=<the original verifier>
A confidential client also sends client_secret, in the
form or via HTTP Basic.
The response:
{
"access_token": "…",
"refresh_token": "…",
"id_token": "…",
"token_type": "Bearer",
"expires_in": 3600
}
A code is single-use and lives 60 seconds. Presenting it twice revokes every refresh token descended from it — a replay means a copy is in somebody else's hands, so the whole chain goes.
3. Using the tokens
GET https://cloud.gerege.mn/openerp/api/v1/contacts
Authorization: Bearer <access_token>
id_tokenis a JWT signed RS256. Verify it against the public key at/.well-known/jwks.json.access_tokenis opaque — do not try to parse it. Ask/oauth2/introspectwhether it is valid.refresh_tokenrotates: each refresh issues a new one. Presenting a spent one revokes the entire chain.
Scopes
| Scope | What it opens |
|---|---|
openid |
Receive an id_token; required to call UserInfo |
profile |
Name and workspace |
email |
E-mail address |
erp.read |
Read business data |
erp.write |
Write business data |
A scope never widens what the person may already do.
Granting erp.write to your app does not let it edit
invoices for somebody who may not edit invoices.
4. UserInfo
GET /openerp/oauth2/userinfo
Authorization: Bearer <access_token>
The claims returned are limited to the scopes that were granted.
Without openid the call is refused.
5. Revoking
POST /openerp/oauth2/revoke (client authentication required)
POST /openerp/oauth2/introspect (RFC 7662, client authentication required)
A person can also withdraw consent themselves, which kills your refresh tokens along with it.
6. Managing a client
Each client on /developer/apps carries four actions. All
of them are bounded by the workspace that registered it
— naming another workspace's client_id is answered exactly the way
naming a client that does not exist is.
6.1 Edit —
PATCH /api/v1/developer/apps/{client_id}
The name, the redirect_uris and the scopes
can be changed.
The redirect URIs are the reason this matters most: one wrong entry
and your app can sign nobody in. They are checked with
exactly the rule registration uses — absolute, no
fragment (#), HTTPS outside loopback — and a bad one is
refused rather than tidied up, because a redirect URI
silently rewritten is a redirect URI you did not register.
grant_types and token_endpoint_auth_method
cannot be edited. Turning a confidential client public,
or back, changes how every token already in circulation was proved; the
honest way to make that change is to register a different client and
move to it.
6.2
Rotate the secret —
POST /api/v1/developer/apps/{client_id}/secret
A new secret is generated and shown once, in that response. The old one stops working at that moment, on every replica, because every client read is a database query rather than a cached copy.
Access and refresh tokens already issued keep working. That is deliberate: the secret authenticates the client at the token endpoint; it is not a key those tokens were derived from. They were issued to the same client, for the same people, under consents nobody has withdrawn. A rotation that also signed every user out would be one an operator hesitates to perform at three in the morning, and the hesitation is the vulnerability.
When your judgement is that the tokens are compromised too, that is a different action: disable the client (below), or withdraw one person's consent.
A public client holds no secret, so this action answers
400 — PKCE is its proof.
6.3 Disable and
enable — POST .../disable,
POST .../enable
A disabled client authenticates nowhere — not at
/oauth2/token, not at /oauth2/auth, not at
introspection or revocation — and every access and refresh token it
holds is revoked.
But nothing is deleted: the token, consent and authorization-code rows stay where they are, so the audit trail outlives the decision to stop the client. The consents stay too — whether to let an app act for them is the person's decision, not an administrator's, so switching the client back on does not ask everybody again. The revoked tokens do not come back; the app collects a fresh set at the next sign-in.
6.4 Delete —
DELETE /api/v1/developer/apps/{client_id}
Only a client that was never used can be deleted. If
it has ever held a token, a refresh token, a consent or an authorization
code, the server answers 409.
Why: three of those tables cascade on the client row, so deleting it
would erase the record of what the client did; the fourth
(oauth2_tokens) does not point at it at all, so its rows
would simply be orphaned and go on working until they expired. Neither
is an acceptable way to take a credential back. Delete is for the
registration made by mistake — wrong name, wrong redirect, five minutes
old, nothing attached to it.
7. Publishing as an external app
To appear in the app store, declare kind: "external" in
your manifest. Installing then:
- creates the OAuth2 client from the manifest automatically (public, PKCE);
- creates the webhook subscription inside the install transaction, so "installed" and "subscribed" can never disagree;
- adds a sidebar entry that opens your app.
Webhooks arrive HMAC-signed. Reinstalling does not rotate the secret — if it did, a secret you had already stored would be silently invalidated.
Such a client is managed by its manifest: it belongs
to no workspace, so it never appears on the /developer/apps
screen and none of the §6 actions can reach it. To change its redirect
URIs or scopes, change the manifest — an edit made from the screen would
be undone at the next boot. If you need a confidential client, register
one through the developer console, where there is somebody to hand the
secret to.
See the module authoring guide.
8. Error responses
| Status | Meaning |
|---|---|
400 |
Bad request — mismatched redirect_uri, missing PKCE,
code_challenge_method other than S256 |
401 |
Token missing, expired or revoked |
403 |
Not permitted — app not installed, or the person lacks that permission |
409 |
Conflict — a code that has already been used, or an attempt to delete a client that has been used (§6.4) |
429 |
Too many attempts |
502 · 503 |
Upstream trouble. 503 carries Retry-After
and means coming back is worth it; 502 means somebody has
to fix something |
The full list is in the API reference.