@cisco-meraki/dashboard-api-tools
v1.0.7
Published
Typescript library for interacting with Meraki's public API
Downloads
30
Readme
@cisco-meraki/dashboard-api-tools
Typescript SDK for interacting with Meraki's API. This library provides an interface for Javascript and Typescript applications to interact with Cisco Meraki's cloud-managed platform.
Features:
- Support for interacting with Meraki's Dashboard endpoints via
apiRequest
. This provides a wrapper around JavaScript's nativefetch
function with features specific to handling Meraki's API responses. - Support for pagination via
makePaginatedRequest
- Supports error handling for Meraki's standard API error responses
- Supports Action Batches via
batchedApiRequest
- Provides a React hook to make API requests directly from React components via
useApiRequest
- Provides a React Toolkit Query base query function via
fetchBaseQuery
- Automatic retries on API requests that fail due to rate limiting errors
Install
yarn add @cisco-meraki/dashboard-api-tools
npm install @cisco-meraki/dashboard-api-tools
How to Use
Making API Requests
apiRequest()
apiRequest
acts as a wrapper around Javascript's fetch
that is strongly-typed and supports Meraki's API-specific features. Note that requests to Meraki's API require an API Key. See the section on Providing API Key below.
Accepted Parameters:
method
: HTTP method of API request. Must be one of "get", "post", "put", "delete", "options", "GET", "POST", "PUT", "DELETE", "OPTIONS"url
: URL of API request,data?
: Optional. Payload body for API request. Do not use this forGET
requests. Instead, any request parameters forGET
requests should be supplied in the URL as query parameters.options?
: OptionalfetchOptions
: Object that contains fields to override default parameters passed tofetch
. This is where you would add any headers that you'd like to send to your API request, which may be useful when trying to troubleshoot CORS issues (see Troubleshooting section below)auth
: Object that contains fields required for authenticating the API requestsapiKey
: User's API key
Response
It will return objects in two possible shapes, wrapped in a promise.
For successful API requests:
Promise<{
firstPageUrl: string | null;
lastPageUrl: string | null;
nextPageUrl: string | null;
prevPageUrl: string | null;
linkHeader: string | null;
retryAfter: number | null;
errors: null;
ok: true;
statusCode: number;
statusText: string;
data: ResponseData
}>
Note that data
is typed as a generic type ResponseData
. This generic type will be passed when calling apiRequest<ResponseData>()
and allows the consumer to set the expected data structure of the API response.
For unsuccessful API requests:
Promise<{
errors: string[];
ok: false;
statusCode: number;
statusText: string;
}>
Providing API Key
In order to interact with the Meraki Dashboard API, you'll need to provide your API key. If you need help obtaining this key, follow the steps in our developer documentation.
await apiRequest(
"POST",
"www.some-url.com/api/v1/endpoint",
{ ... },
{
auth: {
apiKey: <your API key>
}
}
);
Usage
import { apiRequest } from "@cisco-meraki/dashboard-api-tools";
...
const url = `/api/v1/networks/${networkId}/webhooks/webhookTests`
const data = {
url: "https://webhook.site/#!/61296b81-3980-4473-89e9-4b3c8ef0a70e",
payloadTemplateId: "wpt_00002",
}
await apiRequest<ResponseObjectType>("POST", url, data)
Note that ResponseObjectType
is whatever type you expect the response object from the API request to be in.
This function is compatible with either async
/await
or promise chaining
async/await example
try {
await apiRequest("GET", "www.some-url.com/api/v1/endpoint");
/* carry on */
} catch (badResponse) {
/* do something with badResponse.errors */
}
promise chaining example
apiRequest("GET", "www.some-url.com/api/v1/endpoint")
.then(() => /* carry on */)
.catch((badResponse) => /* do something with badResponse.errors */);
isApiError()
For checking errors, the library also provides a type guard helper function to ensure that the errors are in the format we expect before using them. It will verify that the failed response object contains an error
field that is an array of strings.
Accepted Parameters:
response
: The return object received from the call toapiRequest()
Usage
import { apiRequest, isApiError } from "@cisco-meraki/dashboard-api-tools";
...
try {
await apiRequest("GET", "www.some-url.com/api/v1/endpoint");
/* carry on */
} catch (badResponse) {
if(isApiError(badResponse)) {
/* do something with badResponse.errors */
} else {
/* we don't know what format errors are in, so just log them */
}
}
Pagination
The return object of apiRequest()
includes firstPageUrl
, lastPageUrl
, prevPageUrl
and nextPageUrl
fields that can be used by subsequent requests to get paginated data. If you want to automatically make requests to nextPageUrl
across multiple requests, this package provides a helper function for that functionality.
paginatedApiRequest()
This function will make paginated requests to the provided URL based on the perPage
query parameter. It makes a request to the given URL, then makes successive requests to URLs provided in the Link
header from the response. See docs on pagination for more details on the Link
header.
It accepts functions that will be called for each successful response as well as each unsuccessful response.
It also accepts a parameter to set the maximum number of requests allowed for this endpoint to protect against abnormally large or infinite number of successive requests.
The method signature accepts the above 3 values as well as an object with a shape identical to the arguments provided for apiRequest()
Accepted Parameters:
dataHandler
: Callback that is invoked every time a successful response is received. For example, this can be a handler that dispatches a Redux action every time data is received from an endpoint.errorHandler
: Callback that is invoked each time a response returns errorsapiRequestParams
: Object that contains fields for each parameter used forapiRequest()
maxRequests
: Maximum number of paginated requests that will be made before halting all requests. The default value is 9,999.
Usage
import { paginatedApiRequest } from "@cisco-meraki/dashboard-api-tools";
...
const storeClientsInRedux = (clients) => (dispatch) => {
dispatch(actions.updateClients(clients));
};
const storeErrorsInRedux = (errors) => (dispatch) => {
dispatch(actions.clientFetchFailed(errors));
};
await paginatedApiRequest(storeClientsInRedux, storeErrorsInRedux, {method: "GET", url: "www.some-url.com/api/v1/endpoint}, 100);
Action Batches
Action Batches are a special type of Dashboard API mechanism for submitting batched configuration requests in a single synchronous or asynchronous transaction. Action Batches are ideal for bulk configuration, either in the initial provisioning process, or for rolling out wide-scale configuration changes. For example, you could add a switch to a network, configure all 48 ports, and set the switch’s management interface in a single POST request.
batchedApiRequest()
batchedApiRequest
acts as a wrapper for Meraki's Action Batches. When the request is first made and the Action Batch is initially created, the status may be "pending" while waiting for the batches to complete. If so, it will poll the status of the action batch and return either an error or successful response depending on the Action Batch's status.
Accepted Parameters:
organizationId
: Id of the organization to run the series of API requests onactions
: Object that contains information around which API requests to include in the Action Batch. Contains these fields:resource
: URL fragment of API requestoperation
: Must be "create", "update" or "destroy"body
: Payload body for API request
authOptions
: Object that contains fields required for authenticating the API requestsfetchOptions
: Optional options to override parameters passed tofetch
callauth
: Object used to store authentication optionsapiKey
: User's API key
opts?
: Optional. Object that contains extra metadata for how you want the Action Batch to perform. Contains these fields:maxPollingTime
: Maximum time (in ms) before it halts requests that check on Action Batch status. Defaults to 12,000msauth
: Time (in ms) between each request to check if Action Batch is complete. Defaults to 500mssynchronous
: Flag that tells the Action Batch to run synchronously or asynchronously
Response
For successful requests:
Promise<{
id: string;
organizationId: string;
confirmed: boolean;
synchronous: boolean;
status: ActionBatchStatus;
actions: Action[];
}>
For unsuccessful requests:
Promise<{
errors: string[];
ok: false;
statusCode: number;
statusText: string;
}>
Usage
import { batchedApiRequest, isApiError } from "@dashboard-api/api-utils";
...
const actions = [
{
"resource": "/devices/QXXX-XXXX-XXXX/switchPorts/3",
"operation": "update",
"body": { "enabled": true }
};
...
]
const authOptions = {
auth: {
apiKey: "your apiKey
}
}
try {
await batchedApiRequest(organizationId, actions, authOptions)
/* carry on */
} catch (badResponse) {
if (isApiError(badResponse)) {
/* do something with badResponse.errors */
} else {
/* handle the errors in any way of your choice */
}
}
React Hook
If using React and not using a library such as Redux Toolkit Query that provide hooks for you, you may find some use in a custom React hook that provides consistent data fetching across components.
useApiRequest()
You can use this function as a React hook for interacting with Meraki's public API. It is a wrapper around React's useState
and useEffect
hooks and uses isApiError()
to make API requests and format the responses.
It has a method signature that accepts a generic type that represents the expected response object type, as well as two arguments:
apiRequestParams
: An object with a the shape identical to the arguments provided forapiRequest()
dependencies
: A list of dependencies that, when changed, will trigger this hook to run. This is similar to how React's useEffect hook works
It returns 3 values:
response
: The formatted response from the API. It will beundefined
if request was not successful.errors
: Any errors returned from API response. It will beundefined
if request was successful. The hook usesisApiError()
to ensure that the errors returned are wrapped in an array of strings.isFetching
: Status indicating whether the API request completed or not. This is useful for dynamically rendering a loading state in the UI.
Usage
import { useApiRequest } from "@cisco-meraki/dashboard-api-tools";
...
const ComponentUsingHook = () => {
const [response, errors, isFetching] = useApiRequest<SuccessfulResponse>({method: "GET", url: "www.some-url.com/api/v1/endpoint" }, []);
return (
<>
{
isFetching && <div>Loading</div>
}
{
!isFetching && response && <div>{response.data.someValueFromResponse}</div>
}
{
!isFetching && errors && <div>
{
errors.map((error, index) => <div key={index}>{error}</div>)
}
</div>
}
</>
);
};
Redux Toolkit Query Integration
Redux Toolkit Query provides an opinionated pattern for Redux logic in your React applications intended to simplify things for the developer. If your application is using RTK Query, you can use the custom base query function provided.
fetchBaseQuery
RTK Query allows for a custom base query, which is usually just a wrapper around Javascript's native fetch
, to be provided to customize data handling and response objects across endpoints. For this case, we have created the fetchBaseQuery
function that integrates with RTK Query and provides a consistent way of making API requests while still allowing individual endpoints to customize requests and responses as needed.
Because fetchBaseQuery
is using apiRequest()
, we can access all of the response data that we expect from our API responses.
The return object of this function includes these fields:
data
- This is the data returned in the response object from the API request. This comes from thedata
field fromapiRequest()
meta
- This includes all other data fromapiRequest()
that is not part of the response object (i.e.statusCode
, pagination fields, etc.). See documentation above for all fields in the return object formapiRequest()
.
Note that responseHandler
and validateStatus
, which are expected to be part of RTK Query responses, are not yet available when using this custom base query.
Usage
import { fetchBaseQuery } from "@cisco-meraki/dashboard-api-tools";
...
export const merakiApi = createApi({
reducerPath: "merakiApi",
baseQuery: fetchBaseQuery({
baseUrl: "api.meraki.com/api/v1/",
transformHeaders: (headers: Headers) =>{
const preparedHeaders = new Headers(headers);
preparedHeaders.set("Accept", "application/json");
/* add other headers here */
return Promise.resolve(preparedHeaders);
},
}),
endpoints: () => ({}),
});
Troubleshooting
CORS issues
The same-origin policy limits the ability of a browser to use resources from a server outside of its domain to help prevent forging and stealing private data. Because of this, websites can generally only fetch data from their own servers. CORS is a mechanism implemented by browsers that uses HTTP headers that let the user request data across domains.
Any application that you build that wants to fetch Meraki Dashboard data will be on a different domain and will therefore need to account for CORS. Our recommendation is to add a simple CORS proxy to your application.
CORS proxies will allow you to fetch the intended data from Meraki's API. Instead of requesting data directly from a Meraki endpoint, you'll send requests to the CORS proxy server instead. The proxy server will then forward the request to Meraki's API as normal and forward the response back to the client. This server is also a great place to add you API key to Meraki API requests.
Example
import { apiRequest } from "@cisco-meraki/dashboard-api-tools";
...
const fetchOptions = {
headers: {
# This header will need to be supported by your CORS proxy
"Custom-Header-Used-By-Proxy": "https://api.meraki.com/api/v1/organizations",
}
}
# Use the URL of the CORS proxy instead of api.meraki.com
# The CORS proxy will then forward requests to the desired endpoint using "Custom-Header-Used-By-Proxy"
apiRequest("GET", "http://<PROXY-URL>/", undefined, { fetchOptions: fetchOptions })