Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: ESPER API REFERENCE reports_Reports API
x-logo:
url: esper-logo-white.svg
backgroundColor: '#141418'
altText: Logo
description: '# Introduction
Welcome to Esper’s API documentation. The Esper API allows users to accomplish operations outside of the console. Some common uses for our APIs include observing device information and console activity, installing and updating apps, uploading files, and sending files to devices. Our users have also used the API to automate app updates, perform bulk actions, and more. Esper API endpoints use REST-based architecture and return JSON responses. See our documentation for:
* Getting started guides
* Commonly used API examples
* API command examples
* Automated Script examples
* And more
# Authentication
You need to create an API Key to interact with our APIs. Learn more about generating an API Key. You can also learn more about Esper and sign up for an account at esper.io/signup. Some endpoints also require an Enterprise ID. This ID can be found in the API Management section of the Esper console. # Lifecycle Labels
Esper uses lifecycle labels to communicate the support status of each API endpoint. Use the following table to understand what each label means and how to respond. Badge
What it means
Recommended action
Stable
This endpoint is fully supported and safe for production use. No action needed. Beta
This endpoint is publicly available but may change. It is not recommended for production integrations. Use with caution. Monitor the API changelog for updates before relying on this endpoint. Legacy
This endpoint uses an older architecture and is still supported, but a newer equivalent exists. It is not recommended for new integrations. Plan migration to the recommended replacement when possible. See the endpoint description for the alternative. Deprecated
This endpoint has stopped being supported. A removal date may be added at a later time. A replacement is available or the feature is entirely retired. Migrate before the removal date listed in the endpoint description. Do not use this endpoint in new integrations. # Pagination
Responses may be paginated. Use the following parameters to query the data. A full list of parameters can be found on the endpoint’s documentation page. Parameter
Data Type
Explanation
limit
integer
Limit the data returned. Default = 20. offset
integer
Offset to the first item returned. Default = 0. ordering
string
Order the results set by the field name. Varies by endpoint. next
string
Paginate to the next response set. previous
string
Paginate to the last set response. # Errors
We use standard HTTP status codes for success or failure. A typical error response may look something like this:
```json
{
"errors": [],
"message": "error message",
"status": 400
}
```
* `errors` - List of error details
* `message` - Error description
* `status` - HTTP status code
Some common status codes and messages are:
Number
Message
Explanation
200
OK
The request succeeded. 201
Resource creation
A resource was created. 401
Unauthorized
The API key is invalid. 404
Not found
The resource was not found. 429
Rate limit exceeded
Too many requests. Wait a moment and try again. 500
Server error
Internal error. Wait a moment and try again. If the issue persists, contact Esper. See how our systems are doing by checking our status page. # Rate Limits
To ensure quality of service for all customers, we enforce rate limits for API requests. Most customers won’t hit this limit with normal use. In case you experience 429 or rate limit exceeded errors, we recommend the following:
* Try sending requests in batches. * Begin with about 20 requests at a time and building up from there. * Ensure your scripts are efficient and don’t contain redundant calls. * Reach out to your account manager to discuss your options.'
termsOfService: https://esper.io/terms-of-service
contact:
email: developer@esper.io
license:
name: Apache 2.0
url: http://www.apache.org/licenses/LICENSE-2.0.html
version: 1.0.0
x-prefix-tag: esper cloud api
servers:
- url: https://{foo}-api.esper.cloud/api
variables:
foo:
default: develop
description: Custom end point
tags:
- name: reports_Reports API
description: 'APIs for managing reports.
Currently supports App Reports.'
x-displayName: Reports API
paths:
/report/v0/report-types:
get:
tags:
- reports_Reports API
summary: Get report types
description: 'Retrieve the list of report types available in the tenant.
Returns each report type''s internal name, display name, description, the last time it was generated, and the earliest date from which data is available (available_from).
**About Get Report Types**
Esper''s Reports system provides pre-built fleet-level snapshots across devices and apps. Before generating a report, use this endpoint to discover which report types are available (device_apps, device, etc.) and to check when each type was last generated and how far back its data reaches. This is the recommended first step in any automated reporting workflow. Report availibility varies by operating system.
**Key Fields**
name — The internal identifier used in the report_type field when creating a report (e.g., device_apps, device)
display_name — The human-readable label shown in the console
last_generated_at — Timestamp of the most recently completed report of this type
available_from — The earliest date for which data exists for this report type
**Common Use Cases**
Discovering available report types before building an automated reporting pipeline
Checking whether a given report type has been generated recently enough to meet data freshness requirements
Validating the name value to use in the report_type field of a Create Report request
**Best Practices**
Call this endpoint at the start of a reporting workflow to confirm the correct report_type value before posting a report request
Use last_generated_at to assess data freshness; reports reflect ingested data and may lag up to 24 hours behind live device state
Use available_from to validate that your desired date range falls within the supported window before submitting a report
**Workflow**
Call GET /report/v0/report-types to retrieve available types and their metadata
Note the name value for the desired report type (e.g., device_apps or device)
Pass that name as report_type in a POST /report/v0/reports request'
operationId: getReportTypes
security:
- reports_bearerAuth: []
responses:
'200':
description: Report types
content:
application/json:
schema:
$ref: '#/components/schemas/reports_GetReportTypesResponse'
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/reports_ReportsErrorResponse'
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/reports_ReportsErrorResponse'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/reports_ReportsErrorResponse'
/report/v0/reports:
post:
tags:
- reports_Reports API
summary: Create Report
description: 'Initiate generation of a device or device apps report and receive a report ID for status polling. Report availability varies by operating system.
Returns a report_id and an initial status of PENDING; poll GET /report/v0/reports/{report_id} to track progress and retrieve results once the status reaches SUCCEEDED.
**About Create Report**
Report generation in Esper is asynchronous. Submitting this request triggers a backend job that ingests and consolidates fleet data — a process that can take several minutes. The is_download flag controls the output mode: set it to false to retrieve an in-memory preview (up to 10,000 rows) or true to generate a full dataset available via a signed download link. Supported report types are device_apps (Android and iOS/iPadOS/tvOS) and device (Android, iOS, Linux, Windows). Note that app reports exclude sideloaded, preloaded, and SDK-installed apps.
**Key Fields / Request Body**
report_type (required) — device_apps for the Device Apps Report or device for the Device Report
start_time — Report window start in UTC (e.g., 2024-08-28T00:00:00Z); must be after July 1, 2024 for device_apps
end_time — Must equal start_time (single-day reports only)
is_download — true to generate a full downloadable CSV; false for an in-console preview (first 10,000 rows)
filters.date — Optional date override in YYYY-MM-DD format; takes precedence over start_time/end_time if provided
filters.package_name — (device_apps only) Array of package names to filter by; omit for all packages
filters.app_state — (device_apps only) Filter by app visibility: SHOW, HIDE, DISABLE, or LAUNCHABLE_BUT_HIDDEN
filters.platform — Filter by platform: Android, Apple, Linux, Windows (platform availability varies by report type)
**Common Use Cases**
Generating a point-in-time snapshot of all app versions across an Android or iOS fleet
Producing a full CSV export of device state, OS version, location, and provisioning data for all managed platforms
Filtering a device apps report to a specific package name to audit version consistency across devices
**Best Practices**
Set end_time equal to start_time; the API supports single-day reports only
Use filters.date as a simpler alternative to start_time/end_time when generating a same-day report
Set is_download: true when your fleet exceeds 10,000 devices or apps, since the preview response is capped at 10,000 rows
Poll GET /report/v0/reports/{report_id} at a reasonable interval (e.g., every 10–30 seconds) rather than immediately, as report generation can take several minutes
**Workflow**
Call GET /report/v0/report-types to confirm the report_type value and check data availability
POST to /report/v0/reports with the desired report_type, date range, is_download flag, and any filters
Store the returned report_id and poll GET /report/v0/reports/{report_id} until status is SUCCEEDED or FAILED'
operationId: createReportStatus
security:
- reports_bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/reports_Report'
responses:
'200':
description: Create Report
content:
application/json:
schema:
$ref: '#/components/schemas/reports_CreateReportResponse'
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/reports_ReportsErrorResponse'
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/reports_ReportsErrorResponse'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/reports_ReportsErrorResponse'
/report/v0/reports/{report_id}:
get:
tags:
- reports_Reports API
summary: Get report status
description: 'Poll the status of a report and retrieve its results once generation is complete.
Returns the current status of the report job (PENDING, RUNNING, SUCCEEDED, FAILED, CANCELED, or CLOSED) and, on success, either an inline data preview or a signed external download link depending on the is_download flag set when the report was created.
**About Get Report Status**
This endpoint is the second half of Esper''s two-step asynchronous report pattern. After POST /report/v0/reports returns a report_id, poll this endpoint until status reaches a terminal state. When is_download was false, the successful response includes a data_array of up to 10,000 rows along with a schema object describing column names, positions, and types. When is_download was true, the response instead includes an external_link — a signed URL pointing to the full dataset as a downloadable CSV.
**Key Fields**
report_id (path, required) — UUID returned by POST /report/v0/reports
status — Current job state: PENDING (queued), RUNNING (processing), SUCCEEDED (results available), FAILED, CANCELED, or CLOSED
schema.columns — Array of column metadata objects, each with name, position, type_name, and type_text
data_array — (preview mode only) 2D array of row data, capped at 10,000 rows
external_link — (download mode only) Signed URL to the full CSV export
**Common Use Cases**
Polling report status after initiating generation via POST /report/v0/reports
Retrieving inline row data for small to mid-sized fleets (preview mode, ≤10,000 rows)
Obtaining a signed download URL for full fleet exports that exceed the 10,000-row preview cap
**Best Practices**
Poll at a reasonable interval (e.g., every 10–30 seconds) rather than immediately after creating the report, as generation can take several minutes
Check for terminal statuses (SUCCEEDED, FAILED, CANCELED, CLOSED) and stop polling once reached
If status is FAILED, re-submit the report via POST /report/v0/reports rather than retrying the same report_id
For download-mode reports, retrieve the external_link promptly as signed URLs may expire
**Workflow**
Receive the report_id from POST /report/v0/reports
Poll this endpoint at intervals until status is SUCCEEDED (or a failure terminal state)
If preview mode: read data_array for results and schema for column structure; if download mode: fetch the CSV from external_link'
operationId: getReportStatus
parameters:
- $ref: '#/components/parameters/reports_ReportID'
security:
- reports_bearerAuth: []
responses:
'200':
description: Report status
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/reports_CreateReportResponse'
- $ref: '#/components/schemas/reports_GetReportResponsePreview'
- $ref: '#/components/schemas/reports_GetReportResponseDownload'
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/reports_ReportsErrorResponse'
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/reports_ReportsErrorResponse'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/reports_ReportsErrorResponse'
components:
schemas:
reports_DeviceFilter:
type: object
properties:
date:
type:
- string
- 'null'
format: date
example: '2025-03-17'
description: 'Date of report. If provided, this value will override start_time and end_time values. Example: "2025-03-17". Must be a date later than May 17th, 2025.'
platform:
type:
- array
- 'null'
items:
type: string
enum:
- Android
- Apple
- Linux
- Windows
example:
- Android
reports_Report:
type: object
properties:
report_type:
type: string
enum:
- device_apps
- device
example: device_apps
start_time:
description: 'Start time of the report in UTC format. Example: "2024-08-28T00:00:00Z". Must be a date and time later than July 1st, 2024.'
type: string
format: date-time
example: '2024-08-28T00:00:00Z'
end_time:
description: 'End time of the report in UTC format. Example: "2024-08-29T00:00:00Z". Must be a date and time later than July 1st, 2024 and equal to the start_time.'
type: string
format: date-time
example: '2024-08-28T00:00:00Z'
is_download:
type: boolean
filters:
type:
- object
- 'null'
oneOf:
- $ref: '#/components/schemas/reports_DeviceAppsFilter'
- $ref: '#/components/schemas/reports_DeviceFilter'
required:
- report_type
reports_GetReportResponsePreview:
type: object
properties:
content:
type: object
properties:
id:
type: string
status:
type: string
enum:
- PENDING
- RUNNING
- SUCCEEDED
- FAILED
- CANCELED
- CLOSED
schema:
type: object
properties:
column_count:
type: integer
columns:
type: array
items:
type: object
properties:
name:
type: string
position:
type: integer
type_name:
type: string
type_text:
type: string
data_array:
type: array
items:
type: array
items:
type: string
reports_DeviceAppsFilter:
type: object
properties:
date:
type:
- string
- 'null'
format: date
example: '2024-08-28'
description: 'Date of report. If provided, this value will override start_time and end_time values. Example: "2024-08-28". Must be a date later than July 1st, 2024.'
package_name:
type:
- array
- 'null'
items:
type: string
example:
- com.android.chrome
app_state:
type:
- array
- 'null'
items:
type: string
enum:
- SHOW
- HIDE
- DISABLE
- LAUNCHABLE_BUT_HIDDEN
example:
- SHOW
device_os:
type:
- array
- 'null'
items:
type: string
enum:
- Android
- iOS
example:
- Android
platform:
type:
- array
- 'null'
items:
type: string
enum:
- Android
- Apple
- Linux
- Windows
example:
- Android
reports_GetReportTypesResponse:
type: object
properties:
content:
type: object
properties:
report_types:
type: array
items:
type: object
properties:
name:
type: string
display_name:
type: string
description:
type: string
last_generated_at:
type: string
available_from:
type: string
reports_ReportsErrorResponse:
type: object
properties:
status:
type: integer
error:
type: string
message:
type: string
reports_CreateReportResponse:
type: object
properties:
content:
type: object
properties:
id:
type: string
status:
type: string
enum:
- PENDING
- RUNNING
- SUCCEEDED
- FAILED
- CANCELED
- CLOSED
reports_GetReportResponseDownload:
type: object
properties:
content:
type: object
properties:
id:
type: string
status:
type: string
enum:
- PENDING
- RUNNING
- SUCCEEDED
- FAILED
- CANCELED
- CLOSED
schema:
type: object
properties:
column_count:
type: integer
columns:
type: array
items:
type: object
properties:
name:
type: string
position:
type: integer
type_name:
type: string
type_text:
type: string
external_link:
type: string
parameters:
reports_ReportID:
name: report_id
description: Report ID
in: path
required: true
schema:
type: string
format: uuid
securitySchemes:
esper_cloud_api_apiKey:
type: http
scheme: bearer
description: "#### API KEY - Access Token\nAccess token for API is passed as authorization header in calls. You need to generate this from your Esper Console at `<domain>-api.esper.cloud` where foo is the sub-domain name you gave for your Esper Dev environment when you signed up for your Esper account. Please follow the instructions [in this Help article](https://help.esper.io/hc/en-us/articles/12656959508497) to generate an access token.\nOnce you have the access token, you need to send an authorization header as below\n\n```bash\n\n curl -X GET \\\n https://<domain>-api.esper.cloud/api/enterprise/<enterprise_id>/device/ \\\n -H 'Authorization: Bearer <ACCESS_TOKEN>' \\\n -H 'Content-Type: application/json' \\\n```\n\n> Please note the use of keyword **Bearer** before the token value.\n\n\nYou can read more about api key authentication [here](https://help.esper.io/hc/en-us/articles/12656959508497).\n"
pipelines_SCAPIAuthToken:
type: http
scheme: bearer
device_apiKey:
type: http
scheme: bearer
description: "#### API KEY - Access Token\nAccess token for API is passed as authorization header in calls. You need to generate this from your Esper Console at `<domain>-api.esper.cloud` where foo is the sub-domain name you gave for your Esper Dev environment when you signed up for your Esper account. Please follow the instructions [in this Help article](https://help.esper.io/hc/en-us/articles/12656959508497) to generate an access token.\nOnce you have the access token, you need to send an authorization header as below\n\n```bash\n\n curl -X GET \\\n https://<domain>-api.esper.cloud/api/enterprise/<enterprise_id>/device/ \\\n -H 'Authorization: Bearer <ACCESS_TOKEN>' \\\n -H 'Content-Type: application/json' \\\n```\n\n> Please note the use of keyword **Bearer** before the token value.\n\n\nYou can read more about api key authentication [here](https://help.esper.io/hc/en-us/articles/12656959508497).\n"
apps_management_apiKey:
type: http
scheme: bearer
description: "#### API KEY - Access Token\nAccess token for API is passed as authorization header in calls. You need to generate this from your Esper Console at `<domain>-api.esper.cloud` where foo is the sub-domain name you gave for your Esper Dev environment when you signed up for your Esper account. Please follow the instructions [in this Help article](https://help.esper.io/hc/en-us/articles/12656959508497) to generate an access token.\nOnce you have the access token, you need to send an authorization header as below\n\n```bash\n\n curl -X GET \\\n https://<domain>-api.esper.cloud/api/enterprise/<enterprise_id>/device/ \\\n -H 'Authorization: Bearer <ACCESS_TOKEN>' \\\n -H 'Content-Type: application/json' \\\n```\n\n> Please note the use of keyword **Bearer** before the token value.\n\n\nYou can read more about api key authentication [here](https://help.esper.io/hc/en-us/articles/12656959508497).\n"
commands_apiKey:
type: http
scheme: bearer
description: "#### API KEY - Access Token\nAccess token for API is passed as authorization header in calls. You need to generate this from your Esper Console at `<domain>-api.esper.cloud` where foo is the sub-domain name you gave for your Esper Dev environment when you signed up for your Esper account. Please follow the instructions [in this Help article](https://help.esper.io/hc/en-us/articles/12656959508497) to generate an access token.\nOnce you have the access token, you need to send an authorization header as below\n\n```bash\n\n curl -X GET \\\n https://<domain>-api.esper.cloud/api/enterprise/<enterprise_id>/device/ \\\n -H 'Authorization: Bearer <ACCESS_TOKEN>' \\\n -H 'Content-Type: application/json' \\\n```\n\n> Please note the use of keyword **Bearer** before the token value.\n\n\nYou can read more about api key authentication [here](https://help.esper.io/hc/en-us/articles/12656959508497).\n"
drift_apiKey:
type: http
scheme: bearer
description: "#### API KEY - Access Token\nAccess token for API is passed as authorization header in calls. You need to generate this from your Esper Console at `<domain>-api.esper.cloud` where foo is the sub-domain name you gave for your Esper Dev environment when you signed up for your Esper account. Please follow the instructions [in this Help article](https://help.esper.io/hc/en-us/articles/12656959508497) to generate an access token.\nOnce you have the access token, you need to send an authorization header as below\n\n```bash\n\n curl -X GET \\\n https://<domain>-api.esper.cloud/api/enterprise/<enterprise_id>/device/ \\\n -H 'Authorization: Bearer <ACCESS_TOKEN>' \\\n -H 'Content-Type: application/json' \\\n```\n\n> Please note the use of keyword **Bearer** before the token value.\n\n\nYou can read more about api key authentication [here](https://help.esper.io/hc/en-us/articles/12656959508497).\n"
device_onboarding_apiKey:
type: http
scheme: bearer
description: "#### API KEY - Access Token\nAccess token for API is passed as authorization header in calls. You need to generate this from your Esper Console at `<domain>-api.esper.cloud` where foo is the sub-domain name you gave for your Esper Dev environment when you signed up for your Esper account. Please follow the instructions [in this Help article](https://help.esper.io/hc/en-us/articles/12656959508497) to generate an access token.\nOnce you have the access token, you need to send an authorization header as below\n\n```bash\n\n curl -X GET \\\n https://<domain>-api.esper.cloud/api/enterprise/<enterprise_id>/device/ \\\n -H 'Authorization: Bearer <ACCESS_TOKEN>' \\\n -H 'Content-Type: application/json' \\\n```\n\n> Please note the use of keyword **Bearer** before the token value.\n\n\nYou can read more about api key authentication [here](https://help.esper.io/hc/en-us/articles/12656959508497).\n"
tenant_apiKey:
type: http
scheme: bearer
description: "#### API KEY - Access Token\nAccess token for API is passed as authorization header in calls. You need to generate this from your Esper Console at `<domain>-api.esper.cloud` where foo is the sub-domain name you gave for your Esper Dev environment when you signed up for your Esper account. Please follow the instructions [in this Help article](https://help.esper.io/hc/en-us/articles/12656959508497) to generate an access token.\nOnce you have the access token, you need to send an authorization header as below\n\n```bash\n\n curl -X GET \\\n https://<domain>-api.esper.cloud/api/enterprise/<enterprise_id>/device/ \\\n -H 'Authorization: Bearer <ACCESS_TOKEN>' \\\n -H 'Content-Type: application/json' \\\n```\n\n> Please note the use of keyword **Bearer** before the token value.\n\n\nYou can read more about api key authentication [here](https://help.esper.io/hc/en-us/articles/12656959508497).\n"
foundation_AuthToken:
type: apiKey
in: header
name: Authorization
reports_bearerAuth:
type: http
scheme: bearer
geofence_bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
x-tagGroups:
- name: ESPER CLOUD API
tags:
- esper_cloud_api_Company Settings
- esper_cloud_api_Application
- esper_cloud_api_Application V1
- esper_cloud_api_AndroidDevice
- esper_cloud_api_Device Group
- esper_cloud_api_Token (Deprecated)
- esper_cloud_api_Commands V2
- esper_cloud_api_Enterprise Policy
- esper_cloud_api_Geofence
- esper_cloud_api_Content
- esper_cloud_api_User (Deprecated)
- esper_cloud_api_User
- esper_cloud_api_Blueprint (Deprecated)
- esper_cloud_api_Directory Record
- esper_cloud_api_Tile Icon
- esper_cloud_api_Alerts
- esper_cloud_api_Alarms
- esper_cloud_api_Wallpapers
- esper_cloud_api_Device Telemetry
- esper_cloud_api_Daily and Custom Reports
- esper_cloud_api_SSO Connections
- esper_cloud_api_Roles
- esper_cloud_api_Personal Access Token
- name: BLUEPRINTS
tags:
- blueprints_Blueprints API
- blueprints_Custom Actions API
- blueprints_Background Scripts API
- name: PIPELINES
tags:
- pipelines_System Status Code
- pipelines_System
- pipelines_Pipelines
- pipelines_Stages
- pipelines_Target Lists
- pipelines_Targets
- pipelines_Device Groups
- pipelines_Operation Lists
- pipelines_Operations
- pipelines_Pipeline Runs
- pipelines_Stage Runs
- pipelines_Target Runs
- name: DEVICE
tags:
- device_Device
- device_Device Heartbeat
- device_Device Apps
- device_Device Enrollment in EMM
- device_Operations
- device_DeviceOperations
- device_MultiOS Remote Viewer
- name: APPS MANAGEMENT
tags:
- apps_management_VPP License Management
- apps_management_VPP App Management
- apps_management_Tenant Apps
- apps_management_Apple App Store
- apps_management_App List
- apps_management_Webclips
- apps_management_Provisioning Profiles
- apps_management_Preferred Regions
- apps_management_OEM Config Schema
- apps_management_Esper Apps
- apps_management_Tenant Esper Apps
- name: COMMANDS
tags:
- commands_Command Request
- commands_Command Status
- name: DRIFT
tags:
- drift_Converge
- drift_Converge Command
- drift_Blueprint Drift
- name: DEVICE ONBOARDING
tags:
- device_onboarding_DEP Sync Request
- device_onboarding_ABM Provisioning
- name: TENANT
tags:
- tenant_APNs certificate
- tenant_DEP Profile
- tenant_DEP Tokens
- tenant_VPP Token Management
- tenant_Google Enterprise
- tenant_MDM Service Config
- name: FOUNDATION
tags:
- foundation_FoundryBuilds
- foundation_FoundryDeviceModels
- foundation_FoundryEvents
- name: REPORTS
tags:
- reports_Reports API
- name: GEOFENCE
tags:
- geofence_Geofences
- geofence_Device State
x-provenance:
method: harvested
publisher: Esper
source: https://api.esper.io/_bundle/openapi.json?download
note: split by refine-openapis from the provider's own published document, verified by fetch (openapi/_original/*.fetch.json);
stamped by stamp-openapi-provenance.py (roadmap#793)