In Petro.ai 5 we've consolidated data oriented requests into a single endpoint. Previously getting data required making a request to /api/<type>
which resulted in a lot of endpoints and inconsistency in behaviors. Now all you need in order to interact with data is target the /api/data
endpoint.
# Quick Reference
Petro.ai uses a single endpoint to deliver all the data types to the API user.
Querying
POST /api/data/query
{
"type": "Petron",
"query": {
"id": "123"
}
}
Inserting
POST /api/data
{
"type": "Petron",
"data": [
{
"name": "My new Petron"
}
]
}
Updating
PATCH /api/data
{
"type": "Petron",
"query": {
"name": "My new Petron"
},
"body": {
"name": "My updated Petron"
}
}
Replacing
PUT /api/data
{
"type": "Petron",
"data": [
{
"id": "1234",
"name": "This Petron's values were replaced"
},
{
"name": "This is a new Petron"
}
]
}
Deleting Data
DELETE /api/data
{
"type": "Petron",
"query": {
"name": "My new Petron"
}
}
# Query Data
Querying data is done using the /api/data/query
endpoint. It leverages the body
of an HTTP request to provide a richer querying experience than route parameters would provide. A simple id query looks like
// POST /api/data/query
{
"type": "Petron",
"query": {
"id": "1234"
}
}
To query for everything of a specific type, that is done by making a request that looks like:
{
"type": "Petron",
"query": { }
}
Leaving the query object empty indicates that the user wants to find everything.
TIP
The fields in the query object match to the schema of the provided type. You can use the /api/data/schema
endpoint to retrieve an example schema for reference.
Users have the option to provide more advanced query operators by using an object syntax instead of providing a direct value. For example, query above can be rewritten as:
{
"type": "Petron",
"query": {
"id": {
"eq": "1234"
}
}
}
Where the id
object is defining the query operators to use.
Another convenience shorthand is for querying for many items in a single field. For example, a user may want to find Petron's with ids ["1234", "5678"]
. In order to do that you would simply change your query to look like:
// POST /api/data/query
{
"type": "Petron",
"query": {
"id": ["1234", "5678"]
}
}
# Query Options
The query endpoint also accepts an object of options that can be used to augment the return.
Using query options is done by adding an options
object to the request body. An example request with options looks like:
{
"type": "Petron",
"query": { },
"options": {
"limit": 10,
}
}
There are a few fields that users will likely want to use which are skip
and limit
.
name | type | description |
---|---|---|
limit | int | Limit the number of documents returned by the API |
skip | int | Skip x number of documents and return the next y from the API |
sortBy | string | Sort by a field on the provided type |
sortOrder | (-1, 0, 1) | Sort by descending, none, or ascending respectively |
# Query Response
The response objects on successful requests are the same throughout the application. A sample successful response for querying looks something like:
{
"data": [
{
"name": "A Petron",
"description": null,
"notes": null,
"ownerId": null,
"unitsId": null,
"timeZoneId": "UTC",
"dockedApps": [],
"id": "5ef3c4ca53191718ec68026e",
"createdAt": "2020-06-24T21:25:30.725Z",
}
],
"cursor": null,
"empty": false,
"count": 1,
"total": 1,
"activityId": null,
"hasCursor": false,
"success": true,
"message": "",
"errors": [],
"issues": [],
"hasErrors": false,
"insertedIds": [],
"idsNotFound": [],
}
If a request is unsuccesful the success
field on the response will be set to false and there may be additional information in the message
field or errors
field depending on the severity of the error.
The total
indicates how many items were matched with the provided query. total
and count
will only differ whenever the limit
or skip
values are provided in the options. count
is simply a quick property for getting the length of the data array.
# Querying Multiple Fields
Querying multiple fields are treated as SQL and
statements. For example
{
"type": "Petron",
"query": {
"createdAt": {
"gt": "1/1/1900"
},
"updatedAt": {
"lt": "12/31/2199"
}
}
}
Will query for all Petron's that were created after 1/1/1900 but before 12/31/2199.
Currently or
statements aren't supported through the API.
# Multiple Conditions
To support and
s on the same field, the query API supports having multiple operators per field
{
"query": {
"createdAt": {
"gt": "2020-01-01",
"lt": "2020-12-31"
}
}
}
The above example looks for data that was created between 2020-01-01
and 2020-12-31
# Query Operations
Currently the supported query operations are:
- Eq - Query for data that match a value
- Ne - Query for data that don't match a value
- In - Query for data that match elements in an array
- Lt - Filter data with a value less than a provided value
- Lte - Filter data with a value less than or equal to a provided value
- Gt - Filter data with a value greater than a provided value
- Gte - Filter data with a value greater than or equal to a provided value
- Regex - Search for data that match on a provided regular expression
- Bounds - Search for data within a bounding box
- Polygon - Search for data within a polygon
# Using Query Operators
To specify a query operator in the query block use the following.
{
"query": {
"createdAt": {
"gt": "2020-01-01"
}
}
}
# Eq Operator
eq
filter to data that only has the field equal to the provided value
{
"query": {
"id": {
"eq": "1234"
}
}
}
eq
can also be simplified to
{
"query": {
"id": "1234"
}
}
# Ne Operator
ne
filters data where the set field is not the provided value
{
"query": {
"id": {
"ne": "1234"
}
}
}
# In Operator
The in
operator allows a user to query data that have elements "in" the respective list. The in operator can be used in two different ways
Explicitly:
{
"query": {
"id": {
"in": ["1234", "5678"]
}
}
}
or implicitly:
{
"query": {
"id": ["1234", "5678"]
}
}
The implicit form is really a convenient way to build queries without having to maintain the operator object. Under the hood, the shorthand form gets converted into the operator form.
# Lt/Lte Operator
lt
simply filters documents that are less than the specified value
{
"query": {
"numberField": {
"lt": 1500
}
}
}
lte
will include documents that include the field as well as documents that are less than it.
# Gt/Gte Operator
gt
filters documents that are greater than the specified value
{
"query": {
"numberField": {
"gt": 1500
}
}
}
gte
will include documents that include the field as well as documents that are greater than it.
# RegEx Operator
regex
allows a user to define a regular expression to match string fields on. The regex
operator expects a string value that can be a plain string or any valid regex string.
// Sample POST to /api/data/query
{
"type": "Well",
"query": {
"name": {
// No flags
"regex": "TOM.*"
},
"basinName": {
// With flags
"regex": "/tom.*/i"
}
}
}
# Bounds Operator
A bound
operator simply accepts an array containing coordinates to generate a bounding box. The array should contain 4 elements in the sequence of
[
0, //<lower-left-long>,
0, //<lower-left-lat>,
10, //<lower-right-long>,
10 //<lower-right-lat>
]
# Polygon Operator
The polygon
operator is the most complicated. It expects a set of valid GEO Json polygon coordinates in order to find documents that match within it.
WARNING
Querying on a field that isn't a geo point will result in returning all values. Typically
fields that support polygon queries will be marked as location or you can use the /api/data/schema
endpoint to find fields that are labeled as GeoPoint
The polygon operator works solely off of the coordinates of a GEO Json Geometry object. A polygon object in GEO Json looks something like:
{
"type": "Polygon",
"coordinates": [
// exterior of the polygon
[
[ -95.36026, 29.7634972 ],
[ -95.36003, 29.7633604 ],
[ -95.35996, 29.7634489 ],
[ -95.36019, 29.7635886 ],
[ -95.36026, 29.7634972 ]
],
// ... holes in the polygon or other polygons
]
}
With that, a basic query in Petro.ai looks something like
{
"query": {
"location": {
"polygon": [
[
[ -95.36026, 29.7634972 ],
[ -95.36003, 29.7633604 ],
[ -95.35996, 29.7634489 ],
[ -95.36019, 29.7635886 ],
[ -95.36026, 29.7634972 ]
]
]
}
}
}
An example query with a complex polygon (including a hole) looks like
{
"query": {
"location": {
"polygon": [
// polygon exterior
[
[ 2.0, 9.0 ],
[ -0.33, 7.0 ],
[ 0.7, 1.8 ],
[ 5.0, 0.15 ],
[ 9.0, 2.0 ],
[ 3.14, 4.0 ],
[ 2.0, 7.0 ],
[ 8.0, 3.0 ],
[ 9.0, 8.0 ],
[ 9.0, 10.0 ],
[ 2.0, 9.0 ]
],
// hole 0
[
[ 5.0, 8.0 ],
[ 5.0, 7.0 ],
[ 6.0, 7.5 ],
[ 5.0, 8.0 ]
]
]
}
}
}
TIP
To get more familiar with GEO Json this tool and the incredibly brief intro are useful starting points.
# Inserting Data
To insert data to the endpoint, make a POST
request to /api/data
with a body in the following form.
// POST /api/data
{
"type": "Petron",
"data": [
{
"name": "A new petron!"
}
]
}
# Insert Response
Whenever data is inserted successfully, a response that looks something like:
{
"data": [],
"cursor": null,
"empty": true,
"count": 1,
"total": 1,
"activityId": null,
"hasCursor": false,
"success": true,
"message": "",
"errors": [],
"issues": [],
"hasErrors": false,
"insertedIds": [
"5ef3c4ca53191718ec68026e"
],
"idsNotFound": [],
}
will be returned. The insertedIds
field is an unordered list of the document ids that were used to create your data in the database.
count
and total
represent how many documents were inserted. These values shouldn't differ when making an insert request.
# Unique Key Collisions
Some collections have unique indexes that they use to enforce application logic. When inserting data, this can pop up every now and then. If this happens the user will see a message in the response that looks something like:
{
// ...
"message": "An error occurred while inserting into core.Links:A bulk write operation resulted in one or more errors.\r\n E11000 duplicate key error collection: petro-alex-dos.core.Links index: PAI_LinkId_1.0.0 dup key: { : \"5e7a7fe810e71e258c4e733c\", : \"f5daff3f-aa9a-4e51-b7a4-5b02bfd2ff57\", : null }. An item you were inserting into core.Links already has a document",
"errors": [
{
"message": "An item you were inserting into core.Links already has a document"
}
],
}
This may be alarming, but it just means that one of the documents you inserted has a unique key collision.
WARNING
One thing to keep in mind, is that if this error is encountered it will look like it was unsuccessful in inserting the other provided documents. However, the remaining documents were inserted okay. This is a bug and will be addressed in an upcoming release.
# Replacing Data
While replacing data has the same syntax as inserting, the differences reside in the behavior. Documents provided in the data array that already have an id will get replaced, the rest would be inserted.
// PUT /api/data
{
"type": "petron",
"data": [
{
"name" : "petron-to-insert"
},
{
"id": "5f5a744a97cfa50e6ca35b1b",
"name" : "petron-to-replace"
}
]
}
In the above example, the petron-to-insert
will get inserted into the database. Whereas the petron-to-replace
will have its values replaced because it has the id field set.
# Replace Response
The response that the above request makes looks like:
{
"data": [],
"cursor": null,
"empty": true,
"count": 2,
"total": 2,
"activityId": null,
"hasCursor": false,
"success": true,
"message": "",
"errors": [],
"issues": [],
"hasErrors": false,
"insertedIds": [
"5f5a786f97cfa50e6ca35b23"
],
"updatedIds": [],
"deletedIds": [],
"idsNotFound": [],
"referencesRemoved": [],
"referencesUpdated": [],
"referencesInserted": []
}
You'll notice that the insertedIds
has a single element and that the count
field is 2. This is to indicate that the replace was successful and the insert was also successful.
# Updating Data
Updates have been improved since the 4.3.2 release. To define the fields, you'll need to provide them as key-value pairs in the body
option. For example, updating a document with type="doc"
with id=2
and a field named name
you would POST something like this:
// PATCH /api/data
{
"type": "doc",
"query": {
"id": 2
},
"body": {
"name": "Updated Name!"
}
}
# Update Response
Update responses are mainly focused on the count
field. It simply indicates how many documents that the provided query matched on and were updated
{
"data": [],
"cursor": null,
"empty": true,
"count": 1,
"total": 1,
"activityId": null,
"hasCursor": false,
"success": true,
"message": "",
"errors": [],
"issues": [],
"hasErrors": false,
"insertedIds": [],
"updatedIds": [],
"deletedIds": [],
"idsNotFound": [],
}
TIP
The updatedIds
array is a deprecated field. The way updates work now can no longer offer the actual ids of what was affected by a query.
# Deleting Data
Deletes are functionally similar to queries except. The primary difference is that a delete does not accept empty queries. It will return an OK but the result object will have a message indicating that you tried something illegal. Example for deleting a specific document:
// DELETE /api/data
{
"type": "Petron",
"query": {
"id": 123
}
}
# Delete Response
Delete responses are similarly focused on the count
field much like the updated response is.
{
"data": [],
"cursor": null,
"empty": true,
"count": 1,
"total": 1,
"activityId": null,
"hasCursor": false,
"success": true,
"message": "",
"errors": [],
"issues": [],
"hasErrors": false,
"insertedIds": [],
"updatedIds": [],
"deletedIds": [],
"idsNotFound": [],
}
TIP
The deletedIds
array is a deprecated field. The way deletes work now can no longer offer the actual ids of what was affected by a query.
# Utility Endpoints
These endpoints don't really provide application value however, they can be useful when trying to debug or figure out how to structure a request.
# Types
The /api/data
route provides a couple of utility endpoints. The first being the types
endpoint. This endpoint is a GET
only endpoint. It returns a list of strings of the available data types in the platform. These values tie directly with the type
field in all of the data requests that can be made.
Making this request is pretty straight forward in terms of API calls
GET /api/data/types
Sample Response
{
"data": [
"Petron",
"Link",
"Comment",
"UnitsDefinition"
],
...
}
# Schemas
The /api/data/schema
endpoint is useful for getting the structure of an object.
WARNING
Currently this endpoint only returns top level fields of the object.