Jadpo is experimental and not ready for production. These docs follow the compiler's main branch as of 30 Oct 2026. What's built · Install 0.1.0
jadpo
Reference: External services

External services

Acting for each user: OAuth grants

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.

example
// 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 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 stores the OAuth state on the server, against the caller's session or API token.
  2. The provider sends the person back to the generated GET /connections/<name>/callback. It finds the state, exchanges the code with PKCE, stores the grant, and redirects to the frontend's return_to with success or failure.

The callback needs no cookie, so the app's own API-token clients connect the same way. Acting for a user at another service: OAuth grants covers how this touches sign-in.

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 } }. That key is a secret, read and rotated as signing keys are, with its previous key kept (Signing keys). No code can read a grant.

Calling with a grant

Every call names whose grant it uses: on_behalf_of: owner_id. 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 grants bring in no new kind of permission.

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> 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 aren't built yet (What is built).

Markdown for agents: /docs/acting-for-each-user-oauth-grants.md.Built from Jadpo main at a707efb.