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

The package format

On this page

A package is laid out as shared/ is, so one set of rules covers the app and every package, and the compiler checks a package as it checks shared/. A library package:

text
postcodes/v2/    package.jadpo                   the index: description, licence, lowest Jadpo version    types/uk_postcode.jadpo         public type UkPostcode = Text { ... }    failures/invalid_postcode.jadpo    libraries/uk.jadpo              public library uk { parse(...)  normalise(...) }    libraries/uk.test.jadpo         tests beside the file, as in an app    translations/                   its own messages and failure wording    GUIDE.md                        optional: how to use it well    CHANGELOG.md    surface.jadpo_record            generated

A provider package adds provider.jadpo and the folders an API needs:

text
stripe/v2024_06_20/    package.jadpo                          the same two keys as every package    provider.jadpo                         how the API is reached and called    resources/payment_intents.jadpo        imported, corrections merged in    types/payment_intent.jadpo             imported    failures/card_declined.jadpo    events/payment_intent_succeeded.jadpo    actions/start_subscription.jadpo       written: several of its own calls in order    subscribers/renewal.jadpo              written: enriches its own webhook    libraries/tables.jadpo                 written: decoders and helpers    provides/email.jadpo                   only when it fills a capability    recipes/track_parcels/                 wiring examples an app copies in    recordings/payment_intents.create.json real responses that confirm the types    surface.jadpo_record
  • The index lists no files. A file is named after what it declares, so the compiler finds a resource by name and loads only what the app uses.
  • A re-import rewrites only imported files. Written libraries, actions and corrections survive.
  • A package's tests run when it is published, not in every app's jadpo test; the digest guarantees the code is the code that passed.
  • No emails/ folder. Email templates, partials and layouts are the app's own, and an emails/ folder in a package is a compile error.
  • Translations are only in library packages, for their own messages and failures, laid out as a domain's (Packages' text: groups, calls and the surface). An app calls them as Postcodes.translations.<message>(...) and rewords them in its root translations/. Provider packages show users no text, because their failures are always mapped to the app's own, so they carry none.
  • GUIDE.md is optional, and says how to use the package well. Every code example in it is compiled and run when the package is published, and jadpo package show <name> --guide prints it.

The index: package.jadpo

Every package's index has the same form: package Name.vN { }, a /// description above it, and two keys. licence: names its licence, and jadpo: the lowest Jadpo version it needs.

The licence is one of Licence.mit, .apache_2_0, .bsd_2_clause, .bsd_3_clause, .isc, .mpl_2_0, .unlicense and .proprietary. Only a package installed from git may be .proprietary; the public registry refuses it. There is no requires:, because packages have no dependencies, and no list of files.

example
// packages/postcodes/v2/package.jadpo/// UK postcodes: parsing, normalising and checking the outward code.package Postcodes.v2 {    licence: Licence.mit    jadpo: "1.0"}

How the API is reached: provider.jadpo

A package that describes an API has one provider.jadpo: a provider { } declaration with a fixed set of keys. A package without one is a library.

One package describes at most one API, because one API is one connection with one set of hosts and credentials. A vendor with two APIs under different credentials, such as Shopify's Admin and Storefront APIs, is two packages.

Key Says
protocol: How bodies are sent: Protocol.json_http, .form_http, .multipart_http, .aws_json. Required
hosts: live: and sandbox:; the same host twice when the sandbox is a test credential; a host may hold one placeholder filled from a setting ("https://{project_ref}.supabase.co")
runs: A container Jadpo runs instead of a fixed host: image: pinned by digest, port: it listens on inside the container, asks: the outbound hosts it needs (see "Services")
credential: The scheme (CredentialScheme.bearer, a named header, basic, AWS signing), which decides the shape of credential the app gives
credential_header: For CredentialScheme.header, the named header the credential goes in alone ("x-api-key"); required for it and only for it
headers: The fixed headers every request carries, each "Name: value" (["anthropic-version: 2023-06-01"]), never one the driver writes itself
oauth: The provider's authorize, token and revoke addresses, for a connection whose credential is an OAuthGrant (see "Acting for each user")
idempotency: header: (where the key goes), window: (how long the provider keeps a key; required), max_length:, the provider's error codes in_use: (the key's first request is still running), reused: (the key came with a different request) and new_key_on: (the first attempt failed and the key cannot be used again), and replays_failures: true (default false) for a provider that replays a failed answer under the same key. Left out when the API takes no key
error_code: Where the provider's error code is in a failed response
webhooks: check: (the signature scheme), event_id:, event_type:, occurred_at: where the body says when the event happened, header: for SignatureScheme.hmac_sha256, and handshake: where the provider needs one
polls: Operations the runtime calls on a schedule, each with a default interval and the event emitted when the answer changes
erasure: The operation that deletes a person at the provider and the key it takes (by:)
max_concurrent: How many of its calls run at once per app instance
settings: Typed values the app gives on the connection (message_stream:)
imported: The description it was imported from, file:, that file's digest, digest:, and reviewed:, the package's types an agent has reviewed against the provider's documentation

Where the API is comes from exactly one place:

  • hosts: when it is on the internet;
  • runs: when Jadpo runs it;
  • neither, when the app gives host: on the connection: another Jadpo app, a self-hosted product, or a service on Workers.

A connection's host: beside runs: means the service is already running there, so Jadpo doesn't start it. A package with fixed hosts: refuses a host:.

example
// packages/acme_couriers/v1/provider.jadpo/// Acme Couriers' booking and tracking API, written from its HTML docs.provider {    protocol: Protocol.json_http    hosts: {        live: "https://api.acme-couriers.example"        sandbox: "https://sandbox.acme-couriers.example"    }    credential: CredentialScheme.bearer    idempotency: {        header: "Idempotency-Key"        window: 24h        max_length: 255        in_use: "idempotency_key_in_use"        reused: "idempotency_key_reused"    }    error_code: "error.code"}// packages/acme_couriers/v1/resources/bookings.jadpopublic resource bookings {    create: {        method: HttpMethod.post        path: "/v1/bookings"        input: CreateBooking        output: Booking        changes: true        idempotent: true        failures: [SlotTaken]        source: PackageSource.written        docs: "https://docs.acme-couriers.example/api#create-booking"    }}// packages/acme_couriers/v1/types/create_booking.jadpopublic type CreateBooking = Object {    postcode: Text    slot: Text}// packages/acme_couriers/v1/types/booking.jadpopublic type Booking = Object {    id: Text    collect_at: Instant {        wire: Wire.unix_seconds    }}// packages/acme_couriers/v1/failures/slot_taken.jadpopublic type SlotTaken = Failure {    kind: FailureKind.conflict    message: "The collection slot is no longer available."}

idempotent: true on an operation means it is safe to send again: with the package's idempotency: block, because it takes the key; without one, because repeating it as it is changes nothing (a PUT that replaces, a DELETE). An operation of a connection whose credential is an OAuth grant also names the scope it needs, scope: "calendar.events".

Inside the package, its names are written bare. An app names them through the package: AcmeCouriers.Booking, AcmeCouriers.SlotTaken. That holds in a match arm too (AcmeCouriers.SlotTaken -> ...), and for an enum's values, AcmeCouriers.Window.morning.

A failure's code, slot_taken from its name, is the provider's error code. Jadpo reads it from the place in the response that error_code: names.

What is public

  • public marks the package's API, as it marks what a domain offers other domains. A public resource and a public action can be called through the connection. Everything else can be called only by the package's own actions, subscribers and polls. As in an app, public goes on a library, never on a function: every function of a public library is public, and internal helpers such as decoders live in a library without public (Project layout and modules). Internal declarations may change freely between versions. An importer marks every resource public.
  • Everything reachable from the public surface is public, and every compile checks it. A failure a public library's function fails with, a provider operation's failures: and the types in a public signature must themselves be public. Internal failures are mapped or handled before they leave a public declaration. A domain's public actions, queries and libraries follow the same rule.
  • A package's resources and actions share the connection's namespace (stripe.customers, stripe.start_subscription), so a clash between them is an error in the package.
  • Package events are public or not. The app may subscribe to a public event; one without public reaches only the package's own subscribers, so a raw webhook a package enriches can stay internal while its enriched event is public. An importer marks webhook events public; subscribing to a package's non-public event is an error.

No presets, no dependencies

Built-in drivers are configured in app.jadpo, never packaged. A driver is native code in Jadpo's runtime that speaks one protocol. PostgreSQL, Redis, S3, SMTP and OpenID Connect are drivers. A vendor that speaks one of them is that driver with the vendor's settings: Cloudflare R2 is driver: Driver.s3 with its host and region: "auto", Neon is the PostgreSQL driver with its pooling mode, and Gmail is the SMTP driver with its host and port. A vendor with storage and an API is a driver connection plus a package. A driver for one vendor comes in a Jadpo release only where a flow needs native code.

example
// app.jadpoavatars: {    driver: Driver.s3    host: config.r2_host    region: "auto"    bucket: "avatars"    credential: {        key_id: config.r2_key_id        secret: config.r2_secret    }}
  • Each driver's page lists the settings for common services, so an agent looks them up rather than guessing: R2, Backblaze B2, DigitalOcean Spaces, MinIO and Amazon S3; Neon, Supabase and Amazon RDS; Gmail, Microsoft 365 and Amazon SES's SMTP; Google and Microsoft. Each is a working connection, checked with the pages' other examples. jadpo check warns when it recognises a host that is missing one of its settings.
  • Sign-in providers are connections. Google, Microsoft and other OpenID Connect providers are google: { driver: Driver.oidc issuer: client_id: credential: } in connections:, named by sign_in: { providers: [google, github] } (Identity). GitHub and Apple aren't plain OpenID Connect, so they are the built-in Driver.github and Driver.apple.

Packages have no dependencies: a package can't require another.

  • The standard library holds the shared vocabulary, such as money, countries, currencies and phone numbers, which is what packages elsewhere mostly depend on each other for.
  • Anything else is copied. A package that needs another's code includes a copy, checked and tested by the publishing bar like the rest of it.
  • Two packages' types for the same thing are converted at the call, usually in one line.
  • Installing a package is one folder and one line in the lock.

Markdown for agents: /docs/the-package-format.md.Built from Jadpo main at a707efb.