Jadpo is experimental and not ready for production. These docs follow the compiler's main branch as of 1 Nov 2026. What's built · Install 0.2.0
jadpo
Guides: all 11

Guides

Call an outside API

You end with a typed description of another service, called through a connection and faked in tests.

On this page

Jadpo calls another service through a typed description of its API, called a provider package, and a connection that says where to reach it and with which key. Your code calls the service like any other function, with typed input and output, and each way it can fail is a failure you handle. This guide looks up a book's details at a book-data service, Open Shelf, by its ISBN.

You start from an app with one domain, books. Open Shelf is made up for this guide; for a real service, use its documentation, or import its OpenAPI document (below).

Describe the service

The app's own description of a service is a package in shared/providers/<name>/v<version>/, one folder per version of the service's API.

shared/providers/open_shelf/v1/package.jadpo:

jadpo
/// Details of published books, from Open Shelf.package OpenShelf.v1 {    licence: Licence.proprietary    jadpo: "1.0"}

provider.jadpo says how the API is reached: its protocol, its live and sandbox addresses, how the key is sent, and where a refusal's code is in the error body it sends back.

shared/providers/open_shelf/v1/provider.jadpo:

jadpo
/// How Open Shelf's API is reached and called.provider {    protocol: Protocol.json_http    hosts: {        live: "https://api.open-shelf.example"        sandbox: "https://sandbox.open-shelf.example"    }    credential: CredentialScheme.bearer    error_code: "error.code"}

The types the API takes and gives, each public so the app can use them:

shared/providers/open_shelf/v1/types/isbn_lookup.jadpo:

jadpo
/// Which book to look up.public type IsbnLookup = Object {    isbn: Text {        min_length: 10        max_length: 13    }}

shared/providers/open_shelf/v1/types/published_book.jadpo:

jadpo
/// A published book, as Open Shelf describes it.public type PublishedBook = Object {    title: Text {        max_length: 500    }    author: Text {        max_length: 200    }    pages: Int? {        min: 1    }}

Each error code the service documents is a failure, named so that its code, not_published, is the one the service sends:

shared/providers/open_shelf/v1/failures/not_published.jadpo:

jadpo
/// Open Shelf knows no book with that ISBN.public type NotPublished = Failure {    kind: FailureKind.not_found    message: "No book has that ISBN."}

An operation is one call: its method and path, its input and output, and the failures it can give. {isbn} in the path is filled from the input's field of that name.

shared/providers/open_shelf/v1/resources/books.jadpo:

jadpo
public resource books {    /// One book, by its ISBN.    retrieve: {        method: HttpMethod.get        path: "/v1/books/{isbn}"        input: IsbnLookup        output: PublishedBook        failures: [NotPublished]        source: PackageSource.written        docs: "https://docs.open-shelf.example/books"    }}

source: PackageSource.written and docs: say it was written from the service's documentation, and where, so a reviewer can check it. An operation that changes something at the service also says changes: true, and whether it is safe to repeat.

Connect to it

app.jadpo:

jadpo
application {    connections: {        shelf: {            package: OpenShelf.v1            domains: [books]            credential: config.open_shelf_api_key            environment: config.open_shelf_environment        }    }}

domains: lists the domains that may call it. The key and the choice of sandbox or live service are settings:

config.jadpo:

jadpo
config {    /// Open Shelf's API key.    open_shelf_api_key: Text {        secret: true    }    /// Open Shelf's sandbox or its live API.    open_shelf_environment: ProviderEnvironment {        default: ProviderEnvironment.sandbox    }}

The key is a secret: only the connection reads it, and it never appears in a log or an error. On your machine, jadpo config set open_shelf_api_key asks for it at a prompt.

Call it

domains/books/failures/unknown_isbn.jadpo:

jadpo
/// No published book has the ISBN given.type UnknownIsbn = Failure {    kind: FailureKind.not_found    message: "No published book has that ISBN."}

domains/books/types/book_details.jadpo:

jadpo
/// What the library shows about a book before adding it.type BookDetails = Output {    title: Text {        max_length: 500    }    author: Text {        max_length: 200    }}

domains/books/actions/look_up_book.jadpo:

jadpo
use app { shelf }use books/types { BookDetails }use books/failures { UnknownIsbn }use shared/providers/open_shelf/// Looks a book up by its ISBN, to fill in the form for adding it.action look_up_book(isbn: OpenShelf.IsbnLookup.isbn) fails UnknownIsbn -> BookDetails {    var found: OpenShelf.PublishedBook = attempt shelf.books.retrieve(        OpenShelf.IsbnLookup {            isbn: isbn        }    ) {        not_published: UnknownIsbn    }    return BookDetails {        title: found.title        author: found.author    }}
  • use app { shelf } brings in the connection, and shelf.books.retrieve calls the operation through it.
  • The block after the call turns each failure the operation gives into one of your own, keyed by its code. The compiler makes you handle every one, so a refusal you didn't plan for can't reach a caller by accident.
  • A read like this one is retried for you when the service is briefly unavailable, and no call is made while the action holds a database transaction open.

Run jadpo records update. It writes network.jadpo_record, the committed list of every outside address the app can reach, so adding a host shows in review:

network.jadpo_record (written by jadpo records update):

text
// Generated by Jadpo from the hosts the app can reach; update it with `jadpo records update`, never by hand.record network 1connection shelf    for OpenShelf.v1    host https://api.open-shelf.example    host https://sandbox.open-shelf.example

Then check:

shell
jadpo check
output
Not yet confirmed  Record each with `jadpo package record`, which checks a sandbox answer  against its types (agent)    shelf.books.retrievechecked: 1 unconfirmed, 12 files

The check passes. "Not yet confirmed" means no real answer from the service has been checked against the types you wrote. With a sandbox key in .env.local, jadpo package record shelf books.retrieve --input '{"isbn": "9780141439549"}' calls the sandbox once, checks the answer, and keeps it, with its values replaced by placeholders, as a recording in the package. It refuses a connection whose environment is live.

Test it

A test never calls the service. fakes: gives the answers each call gets, in order: a value of the operation's output type, or a failure's code:

domains/books/actions/look_up_book.test.jadpo:

jadpo
use app { shelf }use books/types { BookDetails }use books/failures { UnknownIsbn }use shared/providers/open_shelftest "a published book's details come back" {    fakes: {        shelf.books.retrieve: [            OpenShelf.PublishedBook {                title: "Middlemarch"                author: "George Eliot"                pages: 880            }        ]    }    var details: BookDetails = attempt look_up_book("9780141439549")    assert details.title == "Middlemarch"}test "an ISBN nobody published is the library's own failure" {    fakes: {        shelf.books.retrieve: [not_published]    }    assert look_up_book("9780000000000") fails UnknownIsbn}
shell
jadpo test
output
tested: 2 passed, 2 action tests

In jadpo dev, every call is faked too, so nothing leaves your machine and you need no key: a call gets its recording, or else a made-up value of its output type. List operations in development: { sandbox: [...] } on the connection to send them to the service's sandbox instead.

When the service publishes an OpenAPI document

jadpo package import openapi <file> --as OpenShelf.v1 writes the package from it, mapping fields to Jadpo's standard types where they fit. jadpo check then lists each type the app's calls reach for you to review against the service's documentation, and you mark each one reviewed in provider.jadpo.

Read more

Kinds of package, Connecting in app.jadpo, Calling, Tests and fakes, Provider calls in development and Several changing calls in one action. To call the service from a route, see Build an API; an action that reads no stored record needs a line in policy.jadpo saying who may run it.

Markdown for agents: /guides/guide-call-an-outside-api.md.Built from Jadpo main at e00e61f. Jadpo's CI compiles this guide's code and runs its tests.