Skip to content

V2 API Design Notes

Greg Cobb edited this page Mar 13, 2017 · 1 revision

Overview

The Cloud Foundry V2 family of APIs follow RESTful principles.

The specific high-level goals are as follows:

  • Consistency across all resource URLs, parameters, request/response bodies, and error responses.

  • Partial updates of a resource can be performed by providing a subset of the resources' attributes. This is in contrast to the V1 API which required a read-modify-write cycle to update an attribute.

  • Pagination support for each of the collections.

  • Filtering support for each of the collections.

Authentication

Authentication is performed by providing a UAA Token in the Authorization HTTP header.

Versioning

The API version is specified in the URL, e.g. POST /v2/foo_bars to create a new FooBar using version 2 of the API.

Debugging

The V2 API endpoints may optionally return a GUID in the X-VCAP-Request-ID HTTP header. The API endpoint will ideally log this GUID on all log lines and pass it to associated systems to assist with cross component log collation.

Basic Operations

Operations on resources follow standard REST conventions. Requests and responses for resources are JSON-encoded. Error responses are also JSON encoded.

Common Attributes in Response Bodies

Response bodies have 2 components, a metadata and entity sections.

The following attributes are contained in the metadata section:

Attribute Description
guid Stable id for the resource.
url URL for the resource.
created_at Date/Timestamp the resource was created, e.g. "2012-01-01 13:42:00 -0700"
updated_at Date/Timestamp the resource was updated. null if the resource has not been updated

Creating Resources

POST /v2/foo_bars creates a FooBar.

The attributes for new FooBar are specified in a JSON-encoded request body.

A successful POST results in HTTP 201 with the Location header set to the URL of the newly created resource.

The attributes for the FooBar are returned in a JSON-encoded response body.

Reading Resources

GET /v2/foo_bars/:guid returns the attributes for a specific FooBar.

A successful GET results in HTTP 200. The attributes for the FooBar are returned in a JSON-encoded response body.

Listing Resources

GET /v2/foo_bars lists the FooBars.

Successful GET requests return HTTP 200.

The attributes for the FooBar are returned in a JSON-encoded response body.

Pagination

All GET requests to collections are implicitly paginated, i.e. GET /v2/foo_bars initiates a paginated request/response across all FooBars.

Pagination Response Attributes

A paginated response contains the following attributes:

Attribute Description
total_results Total number of results in the entire data set.
total_pages Total number of pages in the entire dataset.
prev_url URL used to fetch the previous set of results in the paginated response. null on the first call.
next_url URL used to fetch the next set of results in the paginated response. null on the last call.
resources Array of resources as returned by a GET on the resource id.

The resources are expanded by default because in that is what is desired in the common use cases.

Pagination Parameters

The following optional parameters may be specified in the initial GET, or included in the query string to prev_url or next_url.

Parameter Description
page Page from which to start iteration
results-per-page Results to return per page
urls-only If 1, only return a list of urls; do not expand metadata or resource attributes

If the client is going to iterate through the entire dataset, they are encouraged to follow next_url rather than iterating by setting page and results-per-page.

Example:

Request: GET /v2/foo_bars?results-per-page=2

Response:

{
  "total_results": 10029,
  "prev_url": null,
  "next_url": "/v2/an_opaque_url",
  "resources": [
    {
      "metadata": {
        "guid": 5,
        "url": "/v2/foo_bars/5",
        "created_at":"2012-01-01 13:42:00 -0700",
        "updated_at":"2012-01-05 08:31:00 -0700"
      },
      "entity": {
        "name": "some name",
        "instances": 3
      }
    },
    {
      "metadata": {
        "guid": 7,
        "url": "/v2/foo_bars/7",
        "created_at":"2012-01-01 19:45:00 -0700",
        "updated_at":"2012-01-04 20:27:00 -0700"
      },
      "entity": {
        "name": "some other name",
        "instances": 2
      }
    }
  ]
}

Search/Filtering

Searching and Filtering are performed via the q query parameter. The value of the q parameter is a key value pair containing the resource attribute name and the query value, e.g: GET /v2/foo_bars?q=name:some* would return both records shown in the pagination example above.

String query values may contain a * which will be treated at a shell style glob.

Query values may also contain >, <, >=, or <=, e.g. GET /v2/foo_bars?q=instances>2`.

Finally, to pick out records from an array of possible values use GET /v2/foo_bars?q=color IN blue,red Note the spaces (" IN "). Also, the column in question must be a string/text type.

To specify multiple query parameters, use ; as a separator:

GET /v2/foo_bars?q=instances>2;color IN blue,red;mood:happy

The API endpoint may return an error if the resulting query performs an unindexed search.

Of course, everything in the URL must be encoded (e.g. ; is actually %3B).

Deleting Resources

DELETE /v2/foo_bars/:guid deletes a specific FooBar.

A successful DELETE operation results in a 204.

Updating Resources

PUT differs from standard convention. In order to avoid a read-modify-write cycle when updating a single attribute, PUT is handled as if the PATCH verb were used. Specifically, if a resource with URL /v2/foo_bars/99 has attributes

{
  "metadata": {
    "guid": 99,
    "url": "/v2/foo_bars/99",
    "created_at":"2012-01-01 13:42:00 -0700",
    "updated_at":"2012-01-03 09:15:00 -0700"
  },
  "entity": {
    "name": "some foobar",
    "instances": 2,
  }
}

then a PUT /v2/foo_bars/99 with a request body of {"instances":3} results in a resource with the following attributes

{
  "metadata": {
    "guid": 99,
    "url": "/v2/foo_bars/99",
    "created_at":"2012-01-01 13:42:00 -0700",
    "updated_at":"2012-01-05 08:31:00 -0700"
  },
  "entity": {
    "name": "some foobar",
    "instances": 3,
  }
}

A successful PUT results in HTTP 201.

The attributes for the updated FooBar are returned in a JSON-encoded response body.

Note: version 3 of this API might require PUT to contain the full list of required attributes and such partial updates might only be supported via the HTTP PATCH verb.

Associations

N-to-One

Reading N-to-One Associations

N-to-one relationships are indicated by an id and url attribute for the other resource. For example, if a FooBar has a 1-to-1 relationship with a Baz, a GET /v2/FooBar/:guid will return the following attributes related to the associated Baz (other attributes omitted)

{
  "baz_guid": 5,
  "baz_url": "/v2/bazs/5"
}

Setting N-to-One Associations

Setting an n-to-one association is done during the initial POST for the resource or during an update via PUT. The caller only specifies the id, not the url. For example, to update the Baz associated with the FooBar in the example above, the caller could issue a PUT /v2/FooBar/:guid with a body of { "baz_guid": 10 }. To disassociate the resources, set the id to null.

N-to-Many

Reading N-to-Many Associations

N-to-Many relationships are indicated by a url attribute for the other collection of resources. For example, if a FooBaz has multiple Bars, a GET /v2/FooBaz/:id will return the following attribute (other attributes omitted)

{
  "bars_url": "/v2/foo_baz/bars"
}

Setting N-to-Many Associations

Setting an n-to-many association is done either during the initial POST for the resource, or during an update via PUT.

To create the association during a POST or to edit it with a PUT, supply an array of ids. For example, in the FooBaz example above, a caller could issue a POST /v2/foo_baz with a body of { "bar_guid": [1, 5, 10]} to make an initial association of the new FooBaz with Bars with ids 1, 5 and 10 (other attributes omitted). Similarly, a PUT will update the associations between the resources to only those provided in the list.

Adding and removing elements from a large collection would be onerous if the entire list had to be provided every time.

A PUT /v2/foo_baz/1/bars/2 will add bar with id 2 to foobaz with id 1.

A DELETE /v2/foo_baz/1/bars/2 will remove bar with id 2 from foobaz with id 1.

Inlining Relationships

There are common Cloud Foundry use cases that would require a relatively high number of API calls if the relation URLs had to be fetched when traversing a set of resources, e.g. when performing the calls necessary to satisfy a cf apps command line call. In these cases, the caller intends to walk the entire tree of relationships.

To inline relationships, the caller may specify an inline-relations-depth query parameter for a GET request. A value of 0 results in the default behavior of not inlining any of the relations and only URLs as described above are returned. A value of N > 0 results in the direct expansion of relations inline in the response, but URLs are provided for the next level of relations.

For example, in the request below a FooBar has a to-many relationship to Bars and Bars has a to-one relationship with a Baz. Setting the inline-relations-depth=1 results in bars being expanded but not baz.

Request: GET /v2/FooBar/5?inline-relations-depth=1

Response:

{
  "metadata": {
    "guid": 5,
    "url": "/v2/foo_bars/5",
    "created_at":"2012-01-01 13:42:00 -0700",
    "updated_at":"2012-01-05 08:31:00 -0700"
  },
  "entity": {
    "name": "some foobar",
    "bars": [
      {
        "metadata": {
          "guid": 10,
          "url": "/v2/bar/10",
          "created_at":"2012-01-03 11:22:00 -0700",
          "updated_at":"2012-01-07 09:03:00 -0700"
        },
        "entity": {
          "name": "some bar",
          "baz_guid": 99,
          "baz_url": "/v2/bazs/99"
        }
      },
    ]
  }
}

Specifying inline-releations-depth > 1 should not result in an circular expansion of resources. For example, if there is a bidirectional relationship between two resources, e.g. an Organization has many Users and a User is a member of many Organizations, then the response to GET /v2/organizations/:id?inline-relations-depth=10 should not expand the Organizations a User belongs to. Doing so would result in an expansion loop. The User expansion should provide a organizations_url instead.~~

Errors

Appropriate HTTP response codes are returned as part of the HTTP response header, i.e. 400 if the request body can not be parsed, 404 if an operation is requested on a resource that doesn't exist, etc.

In addition to the HTTP response code, an error response is returned in the response body. The error response is JSON-encoded with the following attributes:

Attribute Description
code Unique numeric response code
descriptions Human readable description of the error

Actions

Actions are modeled as an update to desired state in the system, i.e. to start a FooBar resource with id 5 and set the instance count to 10, the caller would PUT /v2/foo_bar/5 with a request body of { "state": "STARTED", "instances": 10 }.

Clone this wiki locally