API: Spec - QutEcoacoustics/baw-server GitHub Wiki

API: Spec

The purpose of this specification is to have a public, reliable, non-generated, document that reliably describes the baw-server rest API.

Previous development efforts left the API with redundancy. This spec defines an ideal state the API should strive to achieve.

A full list of available endpoints can be found here at the swagger endpoints for our APIs. For example the staging SwaggerDoc is at https://api.staging.ecosounds.org/api-docs/index.html.

You can also see the Swagger definition at swagger.yaml.

API standards

Inspired by: https://speakerdeck.com/jeremiahlee/good-to-great-api-design-patterns-beyond-rest

Casing

  • All segments of URLs for endpoints should be in snake_case
  • All querystring parameters should be accepted by the server either in camelCase or snake_case
  • All JSON responses will have their fields encoded in snake_case

Data Types

  • All numbers should be encoded bare (as in 123.345 and not "123.345")
  • All dates should be encoded as strings in the ISO8601 format
    • Offset or Zulu notation should never be omitted
  • Where units are not specified, SI units should be assumed
    • Non-SI units are extremely discouraged

Naming and routing

  • Resources should represent one logical entity
  • A base resource should be pluralised (e.g. audio_events)
  • HATEOAS is encouraged
  • Chatty API encouraged
    • Only the following information should be returned:
      • Information directly associated with the resource
      • Links to associated resources
      • Keys to associated resources
    • Exception:
      • if only one additional field is needed (other than any relevant keys) it may be included
  • Nested resources should be used sparingly
    • Only use nested resources for direct and logical associations
    • Entities with many associations should not be nested (i.e. no/too many logical parent association[s])
  • Similarly: array values are discouraged
    • especially where they could hold large numbers of items
      • the relationship should instead be expressed as a nested route/sub-entity
    • especially where editing the array is possible
      • we don't have a reliable method for updating parts of an array
      • the only option is to replace the entire value
      • the recommended method is to express the collection as a nested route/sub-entity

Standard Response format

All API requests should have a standard response format. This includes:

  • named meta data objects (described in following sections)
    • must contain at least status and message properties.
  • response data, either an array or single object ( [...] | {...} )
{
    "meta": {
        "status": 200,
        "message": "OK"
    },
    "data": []
}

Errors

Errors must:

  • return an appropriate response code
  • return a content type that matches what was requested

APIs may optionally return an 'error details object', which may include links designed to go straight into the page e.g. <a href="{value}">{key}</a>:

"error": {
    "details": "You aren't allowed access, but you can get permission",
    "links": { 
        "sign in": "...",
        "request permissions": "...",
        "confirm your account": "..."
    }
}

Standard CRUD patterns

Each of the following is implemented by standard on an API endpoint. Exceptions should be denoted.

  • GET: /resource
    • returns many 'resource' objects in an array body
  • GET: /resource/{key}
    • returns a single resource uniquely identified by key
  • GET: /resource/new
    • returns a single resource with defaults
  • POST: /resource
    • creates a new resource
    • the newly created resource is returned
  • PUT: /resource/{key}
    • a resource uniquely identified by key is updated
    • it is expected the entire resource is sent
    • the updated resource is returned
  • PATCH: /resource/{key}
    • Updates a subset of the properties uniquely identified by key
  • DELETE: /resource/{key}
    • deletes a resource that is uniquely identified by key
    • See API: Archiving for full semantics of soft-deleteable resources

See API: Media Types.

Specialized patterns

Each of the following is optionally implemented on a standard API endpoint.

Upsert

Sometimes a client wants to make sure a particular payload is persisted whether it already exists or not.

Before a upsert (update or insert) facility was available, a client would need to:

  1. Fetch a resource by filter request (because we can't use a GET if we don't know the id)
  2. Then update or create based on the result of the filter.

This is slow (filter requests are slow) and inefficient (multiple requests) and requires a client to know which keys to find by to avoid unique constraint conflicts.

The alternative is upsert support.

  • Upsert (update or insert) is only selectively enabled for routes that support it
  • Allows a single request to be issues that will update or insert a record
  • And will always return an id (if the request is successful) which can be used to operate on the record from that point on
  • Always uses the PUT verb - which is semantically correct: replace the resource with the one I am providing
  • Is always defined on the collection route (because the assumption is the client does not know the id)
    • e.g. on /projects and not /projects/123

Example:

Doing a PUT /verifications with a body of:

{
    "verification":
    {
         "audio_event_id": 123,
         "tag_id": 456,
         "confirmed": "unsure"
    }
}

Will return either 201 Created when created, with a body of:

{
    "meta": {
        "status": 200,
        "message": "OK"
    },
    "data": {
         "id": 789,
         "audio_event_id": 123,
         "tag_id": 456,
         "confirmed": "unsure"
    }
}

when created, or 200 OK when updated with a body of:

{
    "meta": {
        "status": 200,
        "message": "OK"
    },
    "data": {
         "id": 111,
         "audio_event_id": 123,
         "tag_id": 456,
         "confirmed": "unsure"
    }
}

If you need a resource that supports remote prodcedure calls (RPCs) then consider

a) Redesigning your resource or b) following the pattern in API: Actions document.

Delayed Response

NOT IMPLEMENTED

- For any sort of endpoint
- For requests by browsers
    - either leave request open
    - or present with retry html page
- For API requests
    - Return `retry-after` header
    - Response should include 'delayed response object`, which may include an indication of high load
"delayed_response": {
    "retry_after": 22.0,
    "average_response_time": 13.0,
    "current_average_response_time": 25.0 
}

Sorting (for /resource)

- for query string parameter:
   - `orderBy`=`<field name>`
   - `direction`=`[desc|asc]`
- Response should include 'sorting details object'
"sorting": {
    "orderBy": "field",
    "direction": "desc"
}

Paging (for /resource)

- for query string parameters (all optional):
   - `page` = `<integer greater than 0>` (default 1)
   - `items` = `<integer greater than 0>` (default 25)
   - `disable_paging` = `<boolean>` (default `false`, mutually exclusive with `page` and `items`)
- Response should include 'paging details object'
"paging": {
    "page": 1,
    "items": 25,
    "total": 262,
    "max_page": 11,
    "current": "http:\/\/baw.ecosounds.org\/audio_recordings\/filter?items=25&page=1",
    "previous": null,
    "next": "http:\/\/baw.ecosounds.org\/audio_recordings\/filter?items=25&page=2"
}

Projection (for /resource)

- QSP: `projection_exclude`: comma separated list of resource fields (suggestion, not implemented)
- QSP: `projection_include`: comma separated list of resource fields (suggestion, not implemented)
- Response should include 'projection details object'
"projection": {
    "include": [
        "(resource field 1)",
        "(resource field 2)"
    ]
}

"projection": {
    "exclude": [
        "(resource field 1)",
        "(resource field 2)"
    ]
}

the options pattern

  • any additional parameters that control a resource are considered an option
  • they must not affect filtering, projection, paging, or sorting
  • they should affect the shape of the data
  • when options are included in a QSP they are not prefixed (e.g. ?bucket_size=86400)
  • when options API filter request body they are nested under the options key

Currently there is one proposed option: bucket_size

"options": {
   "bucket_size": 86400
}

Some routes may need to return summary statisitcs, or aggregates over data. See API: Stats

Capabilities are defined in the API: Capabilties document.

Arbitrary filter functions are defined in the API: Filtering document.

⚠️ **GitHub.com Fallback** ⚠️