Calling an external API

Call an instrument or service over HTTP from a skill, and keep the response with your project.

Some workflows need to talk to something outside the robot: trigger a plate reader, poll an instrument for a measurement, tell a service that a step finished. api_request is the supported way to make an HTTP call from inside a skill. Import it with from execution.execution_functions import *.

api_request(
    url,
    method="GET",
    *,
    json_body=None,
    params=None,
    headers=None,
    save_name=None,
    save_to_project=False,
    export=False,
    timeout=30,
)

It calls url with the given method and returns a result dict. Like the other soft-failing runtime calls, it never raises into your skill: a timeout, a connection error, or a non-2xx status comes back as success: False instead of ending the run.

Parameters

  • url (str): the full request URL. There is no base-URL setting. The workflow supplies the address, usually as a workflow input, so the same skill can point at different instruments.
  • method (str, default "GET"): the HTTP method, for example "GET", "POST", or "PUT".
  • json_body (default None): a value sent as the JSON request body, for POST, PUT, or PATCH.
  • params (dict, default None): query-string parameters.
  • headers (dict, default None): request headers.
  • save_name (str, default None): the basename, with no extension, for the saved response. When omitted, nothing is written even if save_to_project or export is True.
  • save_to_project (bool, default False): set True, together with save_name, to write the response to data/api/<run>/<save_name>.json in your project, keyed by run, so it travels with the project and syncs to the cloud. See the project data folder.
  • export (bool, default False): set True, together with save_name, to upload the response to your lab's data export bucket under <prefix>/<project>/<run>/api/<save_name>.json. With no bucket configured, or outside a run, it does nothing and logs one warning.
  • timeout (float, default 30): the per-request timeout, in seconds.

The two saving flags are independent destinations, and you can set both. See Two places run output can go.

Returns

A dict. On success:

{
    "success": True,
    "status": 200,          # HTTP status code
    "data": {...},          # parsed JSON, or the raw text if the body is not JSON
    "saved_path": "...",    # present only when the response was saved
    "t": 1690000000.0,      # request start time, epoch seconds
}

On failure:

{"success": False, "status": None, "data": None, "t": ..., "error": "<message>"}

Branch on result["success"] before you read result["data"].

What gets saved

When save_name is set together with save_to_project or export, the saved file holds the request url and method, the response status, the timestamp, and the parsed data. Request headers are not written, so an auth token you pass in headers does not end up in the file.

🔒

The saved file leaves this machine, either with your project or into your export bucket. Two things follow. Do not put secrets in the url, because it is saved exactly as you passed it. And the response data is written as-is, so if an endpoint returns anything sensitive that goes with it. params and headers are not written to the file, so pass credentials in one of those.

Example

The entry function's name matches the skill id, and its parameters are the skill's inputs. Take the instrument's address as an input so the workflow can set it:

def read_instrument(base_url: str):
    # Trigger a reading, then fetch the result.
    api_request(f"{base_url}/measure", method="POST", save_name="trigger", save_to_project=True)
    res = api_request(f"{base_url}/latest", save_name="reading", save_to_project=True)
    if not res["success"]:
        print_log(f"instrument fetch failed: {res['error']}", runlog=True)
        return {"success": False}
    return {"success": True, "reading": res["data"]}

Both saved responses land under data/api/<run>/ and travel with the project. On a rig whose lab has data export set up, swap save_to_project=True for export=True to put them in your own bucket instead, or pass both to do each.

Related


Did this page help you?