Jadpo is experimental and not ready for production. These docs follow the compiler's main branch as of 5 Nov 2026. What's built · Install 0.3.0
jadpo
ReferenceConnecting and calling

External servicesConnecting and calling

OAuth grants

not builtThis page is the design; Jadpo doesn't do this yet. See What is built.

On this page

Use a grant when your app acts at another service for each of its users or organisations: a user's Google Calendar, an organisation's Xero, a Slack workspace install. Each owner connects their own account, and your app calls the service with that owner's permission.

Signing in proves to your app who a person is. A grant goes the other way: your app acts at another service, with that owner's consent. It isn't a way to sign in. Your app acting as an OAuth provider, for other apps to sign in with, is a third, separate thing.

app.jadpo
// app.jadpocalendar: {    package: GoogleCalendar.v3    domains: [scheduling]    credential: OAuthGrant {        owner: User        client_id: config.google_client_id        client_secret: config.google_client_secret        scopes: ["calendar.events"]        return_to: "/settings/integrations"    }}// domains/scheduling/actions/add_to_calendar.jadpoattempt calendar.events.insert(event, on_behalf_of: booking.organiser_id) {    grant_missing: CalendarNotConnected    grant_revoked: CalendarNotConnected}

The grant is the connection's credential

A connection's credential may be a grant owned by one of the app's records. The owner is User or any entity, such as Organisation. The grant belongs to that record, not to a session, so it outlives every session and is used from subscribers and schedules, which run with no caller. The provider's OAuth facts (its authorize, token and revoke addresses) are oauth: in the package's provider.jadpo, and each operation declares the scope it needs (scope: "calendar.events"). Nothing is specific to one provider, and the connection's name is the app's.

Connecting

Connecting is two generated endpoints, not pages:

  1. POST /connections/<name>/start, called by the signed-in caller, returns the provider's consent address. It needs a signed-in caller, so authentication: RouteAuthentication.none isn't available on it. It stores the OAuth state on the server, against the caller's browser session or API token.
  2. The provider sends the person back to the generated GET /connections/<name>/callback. It finds the state, so a consent can complete only once, for the caller who started it. It exchanges the code with PKCE, stores the grant, and redirects to the frontend's return_to, a path on the app's own site, with success or failure.

The callback needs no cookie, so the app's own API-token clients connect the same way as browsers. Both routes appear in jadpo routes, OpenAPI and the API record, marked generated (Generated routes).

Where grants are kept

The runtime holds grants, as it holds sessions, in a jadpo_grants system table in the owner entity's store, so grants need a database. Their tokens are encrypted with application { grants: { encryption_key: config.grant_encryption_key } }, a SigningKey setting. That key is a secret, rotated as a signing_key: is: the current value encrypts, and its _PREVIOUS value is still read (Signing keys). encryption_key: is one of the places in application { } a secret may be read (Secrets). No code can read a grant.

Calling with a grant

Every call names whose grant it uses: on_behalf_of: owner_id, an id of the owner's entity that the action has loaded or been given. The caller of the route need not be the owner: a scheduled job acting for an organisation names the organisation. grant_missing and grant_revoked are failures the app maps, as the example maps both to CalendarNotConnected.

Tokens are refreshed for you: renewed before they expire, once when several calls race, and a rotated refresh token kept. A revoked grant gives grant_revoked and a GrantRevoked event, so the app can ask the owner to connect again.

Scopes are checked when you compile: calling an operation whose scope the connection doesn't request is an error.

Who may connect

Policy decides who may connect for an owner. Connecting and disconnecting need the update grant on the owner's record. So a user connects their own account, and an organisation's grant needs whatever lets the caller update the organisation. Grants bring in no new kind of permission, and the start route checks this before it answers.

Disconnecting

The generated POST /connections/<name>/disconnect, and its operation, disconnect a grant, revoking it at the provider when the package declares how. Erasing a person or a record (Erasure) deletes and revokes its grants.

In development and tests

Calls are faked by default. jadpo dev grants add <connection> --owner <id>, not built yet, makes a fake grant, and tests declare grants as fixtures.

Later

Each of these comes when an app needs it: several accounts per owner, scopes added after connecting, provider notifications per grant, and GitHub App installation tokens (a credential scheme in the runtime).

OAuth grants, their routes, on_behalf_of: and jadpo dev grants add aren't built yet, and nor is acting as an OAuth provider (What is built).

Markdown for agents: /docs/oauth-grants.md.Built from Jadpo main at 7b64d64.