Skip to contents

Asks the APIs to describe themselves, answering three questions: what can I do at all, where is the thing I want, and what can I send or ask for there.

Usage

api_discover(realm, endpoint = NULL, refresh = FALSE)

Arguments

realm

String. A realm, or a single API's name. A realm covers every API registered to it; because each primary API is named after its realm, "counts" means the realm and returns the superset. Required: there is no sensible default, and asking the wrong service is a confusing way to fail.

endpoint

String. Optional. An exact path returns what that endpoint accepts; a fragment returns the endpoints matching it; omitted, every endpoint is listed. Leading slashes and an api prefix are optional — "events", "/events" and "/api/events" all resolve, as does "fit" on a service whose paths sit at the root.

refresh

Logical. Fetch the descriptions again rather than reusing the copies held for this session. Only useful after an API has been deployed.

Value

A tibble. Listing endpoints — with no endpoint, or with a fragment that matched several:

api

Which service the endpoint belongs to.

endpoint

The path.

methods

The HTTP methods allowed, so "GET, POST" means you can read and create but not update or delete.

For one endpoint, one row per thing you can send:

api, endpoint

Where the row came from.

action

Which method it belongs to.

kind

"body" for a field you send when writing; "path" for a value the URL itself needs, such as an id; "filter", "include", "append", "sort" or "page" for ways to shape a read. Anything outside that vocabulary reports where the description says the value goes, usually "query" — which is how the plumber services declare even a POST's inputs.

name

The field or parameter, as you would pass it.

required

Whether the API's description marks it required. See the caveat below.

values

Accepted values, where the description enumerates them. Mostly populated for include; blank elsewhere means unconstrained or simply undocumented, not that nothing is accepted.

Details

Called with a realm alone it lists every endpoint of every API in that realm. Called with an endpoint too it lists, per method, the fields you can send and the filters, includes, appends, sorts and pagination you can query with. Called with a fragment rather than a path it searches, so you do not have to scroll several hundred endpoints to find one.

It reads the description each API publishes rather than carrying a hard-coded list, so it cannot fall out of step, and it needs no token — you can explore before signing in.

Which APIs a realm covers

A realm's membership comes from the packages installed on this machine. Discovery loads the namespace of every installed package that imports spdgt.auth — it never attaches one, so your search path is untouched — and each package's .onLoad registers the APIs and URLs it owns. Nothing is stored here, so no address can go stale, and a package you have not installed simply does not appear: its endpoints are ones you could not call anyway. The visible side effect is that auth_status() may afterwards list realms you never loaded.

A caveat on required

An API's description is written by hand alongside its validation rules, and the two can drift. At the time of writing, POST /api/individuals describes project_id and species as required. project_id is in fact optional and filled in from your token; species is not a field at all, so it is dropped here rather than offered as one. Treat required as a strong hint rather than the contract, and let a rejected request settle any disagreement — the validation rules are what run.

Examples

if (FALSE) { # \dontrun{
# What can I do at all? Every API in the realm.
api_discover("counts")

# Where are the project endpoints?
api_discover("counts", "project")

# What do I have to send to create one?
api_discover("counts", "/projects") |>
  dplyr::filter(action == "POST", required)

# What can I include on a read?
api_discover("counts", "/surveys") |>
  dplyr::filter(kind == "include")

# One service on its own, paths at the root.
api_discover("sightability", "/fit")
} # }