BrAPI.js is a JavaScript client library for BrAPI. The call style of this library is inspired by D3.js. It can be used in either a browser or a Node.js application. It uses the Fetch API (or node-fetch in Node.js) for making AJAX calls. BrAPI.js is dependent on ES6 classes.
BrAPI.js supports BrAPI versions v1.0
-v2.0
. Currently, it expects a server to use a single version.
# Be sure your version of NPM supports 'prepare' scripts (>[email protected])
# Recommended:
npm install git+https://github.com/solgenomics/BrAPI-js
# Otherwise:
git clone https://github.com/solgenomics/BrAPI-js.git
cd BrAPI.js
npm install .
<!--Browser-->
<script src="build/BrAPI.js" charset="utf-8"></script>
// ES6 import
import BrAPI from './build/BrAPI.js';
// Node.js
const BrAPI = require('./BrAPI.js');
BrAPI.js has been designed to allow for many simultaneous and interdependent calls to BrAPI to be performed asynchronously. In order to do this, data are managed by a class of objects called BrAPINodes. Nodes are organized into a DAG which represents dependancies. Each node represents a transformation on the data. Basic transformations include fork, join, map, reduce, and filter. BrAPI calls are special transformations. Each BrAPI call can be either a fork (calls returning array data) or a map (calls returning single objects). Data interacts through nodes via tasks. When a task completes, it triggers the creation of new task(s) in all child nodes. With the exception of reduce, the operations can be performed independently on each datum. This means one datum can be transformed across multiple nodes while another moves more slowly.
# BrAPI(address, [version, auth_token, call_limit, credentials]) <>
Creates a root BrAPINode. This is the root of a BrAPI.js DAG dataflow. The address should be a string with the base URL of a BrAPI instance that is being queried, i.e. "https://www.yambase.org/brapi/v1". If an auth_token is provided, it will be added as a Bearer header when sending requests over HTTPS. Changing the version determines which deprecation/removal warnings are written the console, it does not restrict functionality. The call_limit parameter specifies how many simultaneous requests may be run by this node and its descendants against the specified server. The credentials parameter allows you to define how HTTP credentials (aka cookies) should be included in a request. See https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials for more information.
var brapi_root = BrAPI("https://www.yambase.org/brapi/v1")
var brapi_root = BrAPI("https://www.yambase.org/brapi/v1", "v1.2", "myToken")
var brapi_root = BrAPI(
"https://www.yambase.org/brapi/v1",
null, //Don't specify version
"myToken",
18 //Allow 18 simultaneous requests
)
If you have multiple different versions of BrAPI for different calls on your server, you may have to create two separate BrAPI handlers. For example, if your search calls are v1.2
and everything else is v1.3
, you would have to use one of the following options:
var brapi_root1 = BrAPI("https://www.myserver.org/brapi/v1","v1.2") // for your search calls
var brapi_root2 = BrAPI("https://www.myserver.org/brapi/v1","v1.3") // for your other calls.
var brapi_root1 = BrAPI("https://www.myserver.org/brapi/v1","v1.2") // for your search calls
.germplasm_search(...)
.server("https://www.myserver.org/brapi/v1","v1.3") // switch for your other calls.
.germplasm_progeny(...)
.each(...);
# node.server(address, [version, auth_token, call_limit, credentials]) <>
Creates and returns a child BrAPINode which changes the BrAPI server instance queried by all descendants.
BrAPI("https://www.yambase.org/brapi/v1")
// Query accessions containing "MyAccession" from yambase.org
.germplasm_search({germplasmName:"MyAccession"})
// ** Switch to yamMarkerBase.net **
.server("https://www.yamMarkerBase.net/brapi/v1")
// Using results from the first query, search the second server
// (one search for each result from the first)
.germplasm_search(function(germ){
return {germplasmName:germ.germplasmName}
});
Creates and returns a child BrAPINode which ignores any completed tasks from it's parent node and creates a new task in each child node for each datum in the array. This is the main way of adding input to the dataflow and is only available as a child of the root node, or on a server node that is the immediate child of a root node.
BrAPI("https://www.yambase.org/brapi/v1")
// Set ["344","567","223"] as data
.data(["344","567","223"])
// perform a germplasm call with each datum as germplasmDbId
.germplasm(function(d){
return {germplasmDbId:d};
})
.all(function(germplasm_objects){
//prints a list of germplasm objects
console.log(germplasm_objects)
});
# node.{{BrAPI_Method}}
(params [, behavior]) <>
A {{BrAPI_Method}}
is be one of the available BrAPI methods. These methods create and return a child BrAPINode which makes the relevant BrAPI calls. Calls are tracked as tasks and occur asynchronously. metadata.asynchStatus
asynchronous BrAPI calls are also supported and a polling time of 15 seconds is used. Parameters (either URL parameters or body parameters) are specified for the call using the params argument. The params argument can either be an object, or a function. If it is a function, a separate BrAPI call will be made for each datum passed by the parent node. Otherwise, a single call (or series of calls for each page) will be made and the input data will be ignored. For each datum created, the full response from which it was extracted is available via datum.__response
.
There are two special parameters specific to BrAPI.js. The first, pageRange
(an array [first_page, last_page]
), controls pagination and must be used in place of the usual BrAPI page
parameter. pageRange
defaults to [0,Infinity]
. The second, HTTPMethod
, allows one to override the default HTTP method (e.g. "POST","GET") for a BrAPI method.
For calls which return a response containing a data
array, (i.e. node.germplasm_search(...) ), the behavior argument determines how that data is handled. The default behavior is "fork"
.
- If behavior ==
"fork"
, each object in eachpage_response.results.data
array will be treated as an individual datum. - If behavior ==
"map"
, thepage_response.results.data
array from each response will be concatenated into the initial response's data array and the resultingpage_response.results
object will be considered a single datum. For calls which do not return adata
array, theresponse.results
object is always treated as a single datum.
Wether a call is a BrAPI 'asynch' call will be determined automatically from the response. By default, polling occurs every 15 seconds. To vary the polling times, or check the status, one can use this method which is available only on brapi call nodes. The callback function receives the full json response as the only argument. If it returns a non-null value greater than zero, the next poll with occur after that many milliseconds. If multiple poll methods are chained, they will execute in order and the last non-null return value greater than zero will be used as the polling time.
//to poll the allelematrix_search call every 10 seconds
BrAPI("https://www.yambase.org/brapi/v1")
.data(["1038","4542","45534"])
.allelematrices_search(function(d){
return {markerprofileDbId:d};
}).poll(function(response){
console.log(response.metadata);
return 10000;
});
Creates and returns a child BrAPINode which transforms each datum in a manner similar to Array.prototype.map(). The callback function is called with two arguments (datum, key) for each datum, and may return any value which will be passed to child nodes as a single datum.
# node.fork(node [, node, ...]) <>
Creates and returns a child BrAPINode which transforms each datum in a manner similar to node.map(), however, the callback function should return an array. Each item of each returned array will be passed to child nodes as a datum.
Creates and returns a child BrAPINode which transforms each datum in a manner similar to Array.prototype.filter(). The callback function is called with two arguments (datum, key) for each datum, returning true
will pass the datum to child nodes while returning false
will remove it from the dataflow.
# node.reduce(callback [, initialValue]) <>
Creates and returns a child BrAPINode which transforms each datum in a manner similar to Array.prototype.reduce(). The callback function is called with two arguments (accumulator, datum) and should return the modified accumulator. The result from the reduction will be passed to child nodes as a single datum.
# node.join(node [, node, ...]) <>
Creates and returns a child BrAPINode which takes the output datum from multiple BrAPINodes and joins them into arrays on their keys which are then passed to child nodes. Datum are assigned keys in two different manners. First, when a key-modifying node (a fork node, a reduce node, or a BrAPI node with the behavior of "fork" or called with a single parameter object) transforms data, it will assign each datum a new key in order to maintain uniqueness. Second, keys can be manually assigned using node.keys(...) to create a keys node. node.join(...) will only join nodes which share their most recent key-modifying ancestor, or which have all had their keys manually assigned.
Creates and returns a child BrAPINode which transforms each datum in a manner similar to node.map(). However, instead of modifying the data, it will modify the key for each datum. The callback function is called with two arguments (datum, currentKey) and should return a new key to replace currentKey. Keys returned by the callback function may be any value. If the callback function returns any type other than a string, it will converted to a string before being used as a key. A single key string should not be assigned to two separate datum, doing so will result in errors later in the dataflow.
This method registers a callback function which is called each time the node completes the transformation of a datum. The callback function is called with the arguments (datum, key).
This method registers a callback function which is called once a node has loaded all data. The callback function is called with a single argument data, which is an array of all data sorted by their keys lexicographically.
BrAPI.js Method | BrAPI Call (<= v1.3) | BrAPI Call (v2.0) | Default HTTPMethod |
---|---|---|---|
node.allelematrices_search(params,...) | /allelematrices-search (>=v1.2) or /allelematrix-search (<v1.2) |
POST |
|
node.allelematrices(params,...) | /allelematrices |
GET |
|
node.attributes_categories(params,...) | /attributes_categories |
/attributes/categories |
GET |
node.attributes_detail(params,...) | /attributes/{attributeDbId} |
GET |
|
node.attributes_modify(params,...) | /attributes/{attributeDbId} |
PUT |
|
node.attributes_store(params,...) | /attributes |
POST |
|
node.attributes(params,...) | /attributes |
/attributes |
GET |
node.attributevalues_detail(params,...) | /attributevalues/{attributeValueDbId} |
GET |
|
node.attributevalues_modify(params,...) | /attributevalues/{attributeValueDbId} |
PUT |
|
node.attributevalues_store(params,...) | /attributevalues |
POST |
|
node.attributevalues(params,...) | /attributevalues |
GET |
|
node.breedingmethods_detail(params,...) | /breedingmethods/{breedingMethodDbId} |
/breedingmethods/{breedingMethodDbId} |
GET |
node.breedingmethods(params,...) | /breedingmethods |
/breedingmethods |
GET |
node.calls(params,...) | /calls (server info) |
GET |
|
node.serverinfo(params,...) | /serverinfo |
GET |
|
node.calls(params,...) | /calls (genotyping calls) |
GET |
|
node.callsets_calls(params,...) | /callsets/{callSetDbId}/calls |
GET |
|
node.callsets_detail(params,...) | /callsets/{callSetDbId} |
GET |
|
node.callsets(params,...) | /callsets |
GET |
|
node.commoncropnames(params,...) | /commoncropnames (>=v1.2) or /crops (<v1.2) |
/commoncropnames |
GET |
node.crosses_modify(params,...) | /crosses |
PUT |
|
node.crosses_store(params,...) | /crosses |
POST |
|
node.crosses(params,...) | /crosses |
GET |
|
node.crossingprojects_detail(params,...) | /crossingprojects/{crossingProjectDbId} |
GET |
|
node.crossingprojects_modify(params,...) | /crossingprojects/{crossingProjectDbId} |
PUT |
|
node.crossingprojects_store(params,...) | /crossingprojects |
POST |
|
node.crossingprojects(params,...) | /crossingprojects |
GET |
|
node.events(params,...) | /events |
GET |
|
node.germplasm_attributes(params,...) | /germplasm/{germplasmDbId}/attributes |
GET |
|
node.germplasm_detail(params,...) | /germplasm/{germplasmDbId} |
/germplasm/{germplasmDbId} |
GET |
node.germplasm_markerprofiles(params,...) | /germplasm/{germplasmDbId}/markerprofiles |
GET |
|
node.germplasm_mcpd(params,...) | /germplasm/{germplasmDbId}/mcpd |
GET |
|
node.germplasm_modify(params,...) | /germplasm/{germplasmDbId} |
PUT |
|
node.germplasm_pedigree(params,...) | /germplasm/{germplasmDbId}/pedigree |
/germplasm/{germplasmDbId}/pedigree |
GET |
node.germplasm_progeny(params,...) | /germplasm/{germplasmDbId}/progeny |
/germplasm/{germplasmDbId}/progeny |
GET |
node.germplasm_search(params,...) | /germplasm-search |
/search/germplasm |
POST-->GET |
node.germplasm_store(params,...) | /germplasm |
POST |
|
node.germplasm(params,...) | /germplasm |
/germplasm |
GET |
node.images_detail(params,...) | /images/{imageDbId} |
/images/{imageDbId} |
GET |
node.images_imagecontent(params,...) | /images/{imageDbId}/imagecontent |
PUT |
|
node.images_imagecontent_modify(params,...) | /images/{imageDbId}/imagecontent |
PUT |
|
node.images_modify(params,...) | /images/{imageDbId} |
PUT |
|
node.images_store(params,...) | /images |
POST |
|
node.images(params,...) | /images |
/images |
GET |
node.lists_detail(params,...) | /lists/{listDbId} |
/lists/{listDbId} |
GET |
node.lists_modify(params,...) | /lists/{listDbId} |
PUT |
|
node.lists_items_store(params,...) | /lists/{listDbId}/items |
POST |
|
node.lists_store(params,...) | /lists |
POST |
|
node.lists(params,...) | /lists |
/lists |
GET |
node.locations_detail(params,...) | /locations/{locationDbId} |
/locations/{locationDbId} |
GET |
node.locations_modify(params,...) | /locations/{locationDbId} |
PUT |
|
node.locations_store(params,...) | /locations |
POST |
|
node.locations(params,...) | /locations |
/locations |
GET |
node.maps_detail(params,...) | /maps/{mapDbId} |
/maps/{mapDbId} |
GET |
node.maps_linkagegroups_detail(params,...) | /maps/{mapsDbId}/positions/{linkageGroupId} |
GET |
|
node.maps_linkagegroups(params,...) | /maps/{mapDbId}/linkagegroups |
GET |
|
node.maps_positions(params,...) | /maps/{mapsDbId}/positions |
GET |
|
node.maps(params,...) | /maps |
/maps |
GET |
node.markerpositions(params,...) | /markerpositions |
GET |
|
node.markerprofiles_detail(params,...) | /markerprofiles/{markerprofileDbId} |
GET |
|
node.markerprofiles_search(params,...) | /markerprofiles-search |
POST |
|
node.markerprofiles(params,...) | /markerprofiles |
GET |
|
node.markers_detail(params,...) | /markers/{markerDbId} |
GET |
|
node.markers_search(params,...) | /markers-search |
POST |
|
node.markers(params,...) | /markers |
GET |
|
node.methods_detail(params,...) | /methods/{methodDbId} |
/methods/{methodDbId} |
GET |
node.methods_modify(params,...) | /methods/{methodDbId} |
PUT |
|
node.methods_store(params,...) | /methods |
POST |
|
node.methods(params,...) | /methods |
/methods |
GET |
node.observationlevels(params,...) | /observationlevels (>=v1.2) or /observationLevels (<v1.2) |
/observationlevels |
GET |
node.observations_modify(params,...) | /observations/{observationDbId} |
PUT |
|
node.observations_detail(params,...) | /observations/{observationDbId} |
GET |
|
node.observations_modify_multiple(params,...) | /observations |
PUT |
|
node.observations_store(params,...) | /observations |
POST |
|
node.observations_table(params,...) | /observations/table |
GET |
|
node.observations(params,...) | /observations |
GET |
|
node.observationunits_modify(params,...) | /observationunits/{observationUnitDbId} |
PUT |
|
node.observationunits_detail(params,...) | /observationunits/{observationUnitDbId} |
GET |
|
node.observationunits_modify_multiple(params,...) | /observationunits |
PUT |
|
node.observationunits_store(params,...) | /observationunits |
POST |
|
node.observationunits_table(params,...) | /observationunits/table |
GET |
|
node.observationunits(params,...) | /observationunits |
/observationunits |
GET |
node.ontologies(params,...) | /ontologies |
/ontologies |
GET |
node.people_detail(params,...) | /people/{personDbId} |
/people/{personDbId} |
GET |
node.people_modify(params,...) | /people/{personDbId} |
PUT |
|
node.people_store(params,...) | /people |
POST |
|
node.people(params,...) | /people |
/people |
GET |
node.phenotypes_search_csv(params,...) | /phenotypes-search/csv |
POST |
|
node.phenotypes_search_table(params,...) | /phenotypes-search/table |
POST |
|
node.phenotypes_search_tsv(params,...) | /phenotypes-search/tsv |
POST |
|
node.phenotypes_search(params,...) | /phenotypes-search |
POST |
|
node.phenotypes(params,...) | /phenotypes |
POST |
|
node.plannedcrosses_modify(params,...) | /plannedcrosses |
PUT |
|
node.plannedcrosses_store(params,...) | /plannedcrosses |
POST |
|
node.plannedcrosses(params,...) | /plannedcrosses |
GET |
|
node.programs_detail(params,...) | /programs/{programDbId} |
GET |
|
node.programs_modify(params,...) | /programs/{programDbId} |
PUT |
|
node.programs_store(params,...) | /programs |
POST |
|
node.programs(params,...) | /programs |
/programs |
GET |
node.references_bases(params,...) | /references/{referenceDbId}/bases |
GET |
|
node.references_detail(params,...) | /references/{referenceDbId} |
GET |
|
node.references(params,...) | /references |
GET |
|
node.referencesets_detail(params,...) | /referencesets/{referenceSetDbId} |
GET |
|
node.referencesets(params,...) | /referencesets |
GET |
|
node.samples_detail(params,...) | /samples/{sampleId} |
/samples/{sampleDbId} |
GET |
node.samples_modify(params,...) | /samples/{sampleDbId} |
PUT |
|
node.samples_store(params,...) | /samples |
POST |
|
node.samples(params,...) | /samples |
/samples |
GET |
node.scales_detail(params,...) | /scales/{scaleDbId} |
/scales/{scaleDbId} |
GET |
node.scales_modify(params,...) | /scales/{scaleDbId} |
PUT |
|
node.scales_store(params,...) | /scales |
POST |
|
node.scales(params,...) | /scales |
/scales |
GET |
node.search_attributes(params,...) | /search/attributes |
POST-->GET |
|
node.search_attributevalues(params,...) | /search/attributevalues |
POST-->GET |
|
node.search_calls(params,...) | /search/calls |
POST-->GET |
|
node.search_callsets(params,...) | /search/callsets |
POST-->GET |
|
node.search_germplasm(params,...) | /germplasm-search |
/search/germplasm |
POST-->GET |
node.search_GET(entity, params,...) | /search/{entity}/{searchResultDbId} |
/search/{entity}/{searchResultsDbId} |
GET |
node.search_images(params,...) | /search/images |
/search/images |
POST-->GET |
node.search_lists(params,...) | /search/lists |
POST-->GET |
|
node.search_locations(params,...) | /search/locations |
POST-->GET |
|
node.search_markerpositions(params,...) | /search/markerpositions |
POST-->GET |
|
node.search_markers(params,...) | /markers-search |
/search/markers |
POST-->GET |
node.search_observations(params,...) | /search/observations |
POST-->GET |
|
node.search_observationtables(params,...) | /search/observationtables |
POST-->GET |
|
node.search_observationunits(params,...) | /search/observationunits |
/search/observationunits |
POST-->GET |
node.search_people(params,...) | /search/people |
POST-->GET |
|
node.search_POST(entity, params,...) | /search/{entity} |
POST |
|
node.search_programs(params,...) | /programs-search |
/search/programs |
POST-->GET |
node.search_references(params,...) | /search/references |
POST-->GET |
|
node.search_referencesets(params,...) | /search/referencesets |
POST-->GET |
|
node.search_samples(params,...) | /samples-search |
/search/samples |
POST-->GET |
node.search_studies(params,...) | /studies-search |
/search/studies |
POST-->GET |
node.search_trials(params,...) | /search/trials |
POST-->GET |
|
node.search_variants(params,...) | /search/variants |
POST-->GET |
|
node.search_variantsets(params,...) | /search/variantsets |
POST-->GET |
|
node.search(entity, params,...) | /search/{entity-->search/{entity}/{searchResultDbId} |
/search/{entity-->search/{entity}/{searchResultsDbId} |
POST-->GET |
node.seasons_detail(params,...) | /seasons/{seasonDbId} |
GET |
|
node.seasons_modify(params,...) | /seasons/{seasonDbId} |
PUT |
|
node.seasons_store(params,...) | /seasons |
POST |
|
node.seasons(params,...) | /seasons |
/seasons |
GET |
node.seedlots_detail_transactions(params,...) | /seedlots/{seedLotDbId}/transactions |
GET |
|
node.seedlots_detail(params,...) | /seedlots/{seedLotDbId} |
GET |
|
node.seedlots_modify(params,...) | /seedlots/{seedLotDbId} |
PUT |
|
node.seedlots_store(params,...) | /seedlots |
POST |
|
node.seedlots_transactions_store(params,...) | /seedlots/transactions |
POST |
|
node.seedlots_transactions(params,...) | /seedlots/transactions |
GET |
|
node.seedlots(params,...) | /seedlots |
GET |
|
node.studies_detail(params,...) | /studies/{studyDbId} |
/studies/{studyDbId} |
GET |
node.studies_germplasm(params,...) | /studies/{studyDbId}/germplasm |
GET |
|
node.studies_layouts(params,...) | /studies/{studyDbId}/layouts |
/studies/{studyDbId}/layout |
GET |
node.studies_modify(params,...) | /studies/{studyDbId} |
PUT |
|
node.studies_observations_modify(params,...) | PUT /studies/{studyDbId}/observations (>=v1.1) or /studies/{studyDbId}/observations (<v1.1) |
POST |
|
node.studies_observations_zip(params,...) | /studies/{studyDbId}/observations/zip |
POST |
|
node.studies_observations(params,...) | /studies/{studyDbId}/observations |
GET |
|
node.studies_observationvariables(params,...) | /studies/{studyDbId}/observationvariables |
GET |
|
node.studies_store(params,...) | /studies |
POST |
|
node.studies_table_add(params,...) | /studies/{studyDbId}/table |
POST |
|
node.studies_table(params,...) | /studies/{studyDbId}/table |
GET |
|
node.studies(params,...) | /studies |
/studies |
GET |
node.studytypes(params,...) | /studytypes (>=v1.1) or /studyTypes (<v1.1) |
/studytypes |
GET |
node.traits_detail(params,...) | /traits/{traitDbId} |
/traits/{traitDbId} |
GET |
node.traits_modify(params,...) | /traits/{traitDbId} |
PUT |
|
node.traits_store(params,...) | /traits |
POST |
|
node.traits(params,...) | /traits |
/traits |
GET |
node.trials_detail(params,...) | /trials/{trialDbId} |
/trials/{trialDbId} |
GET |
node.trials_modify(params,...) | /trials/{trialDbId} |
PUT |
|
node.trials_store(params,...) | /trials |
POST |
|
node.trials(params,...) | /trials |
/trials |
GET |
node.variables_datatypes(params,...) | /variables/datatypes |
GET |
|
node.variables_detail(params,...) | /variables/{observationVariableDbId} |
/variables/{observationVariableDbId} |
GET |
node.variables_modify(params,...) | /variables/{observationVariableDbId} |
PUT |
|
node.variables_search(params,...) | /variables-search |
/search/variables |
POST |
node.variables_store(params,...) | /variables |
POST |
|
node.variables(params,...) | /variables |
/variables |
GET |
node.variants_calls(params,...) | /variantsets/{variantSetDbId}/calls |
GET |
|
node.variants_detail(params,...) | /variants/{variantDbId} |
GET |
|
node.variants(params,...) | /variants |
GET |
|
node.variantsets_calls(params,...) | /variants/{variantDbId}/calls |
GET |
|
node.variantsets_callsets(params,...) | /variantsets/{variantSetDbId}/callsets |
GET |
|
node.variantsets_detail(params,...) | /variantsets/{variantSetDbId} |
GET |
|
node.variantsets_extract_store(params,...) | /variantsets/extract |
POST |
|
node.variantsets_variants(params,...) | /variantsets/{variantSetDbId}/variants |
GET |
|
node.variantsets(params,...) | /variantsets |
GET |
|
node.vendor_orders_plates(params,...) | /vendor/orders/{orderId}/plates |
/vendor/orders/{orderId}/plates |
GET |
node.vendor_orders_results(params,...) | /vendor/orders/{orderId}/results |
/vendor/orders/{orderId}/results |
GET |
node.vendor_orders_status(params,...) | /vendor/orders/{orderId}/status |
/vendor/orders/{orderId}/status |
GET |
node.vendor_orders_store(params,...) | /vendor/orders |
POST |
|
node.vendor_orders(params,...) | /vendor/orders |
/vendor/orders |
GET |
node.vendor_plates_detail(params,...) | /vendor/plates/{submissionId} |
/vendor/plates/{submissionId} |
GET |
node.vendor_plates_search(params,...) | /vendor/plates-search (>=v1.2) or /vendor/plate-search (<v1.2) |
POST |
|
node.vendor_plates(params,...) | /vendor/plates |
/vendor/plates |
POST |
node.vendor_specifications(params,...) | /vendor/specifications |
/vendor/specifications |
GET |