Skip to main content
Version: Draft

Uniform Resource Identifiers (URI)

URI Construction

URI construction is important in that it is the door through which consumers enter to obtain API resources. It should be intuitive and easy to guess what an endpoint does just by looking at the URI and HTTP verb, without needing to see a query string.

Endpoint URLs SHOULD advertise resources, and avoid verbs.💡

The table below provides an overview on the elements to be included in the URI.

LevelNameCardinality
0[basePath]1..1 MUST be provided
1protocol|standard0..1 MUST if the implementation is a FHIR API
2protocol version0..1 SHOULD be provided if protocol|standard is provided and relevant
3namespace0..1 SHOULD be provided
4version0..1 MAY be provided
5resource1..1 MUST be provided
6resource-id0..1 MUST be provided when interacting with a Resource Instance
7sub-resource0..1 MUST be provided when interacting with a Sub-resource
8sub-resource-id0..1 MUST be provided when interacting with a Sub-resource instance

Examples

[basePath]/fhir/r4b/nhi/v1/Patient/ZZZ008

[basePath]/fhir/r4b/air/v1/Immunization/_search

[basePath]/fhir/r4b/nzps/v1/Patient/$summary

[basePath]/contracts/v2/laboratories/33245/locations/P100782

[basePath]/v3/vaccinators/43265/certifications/7281

[basePath]/openid-connect/token

In some situations an implemented standard may define the URI structure for you. A good example of this is Fast Health Interoperability Resources (FHIR). For additional guidance on the use of FHIR see Part D: FHIR API Design and Development Standards.

API Offering

It is RECOMMENDED that the URL makes it clear that it is an API:💡

Examples

[https://api.example.govt.nz]

[https://example.govt.nz/api]

Version

APIs should have a clear indication of the version, so that application developers can ensure they are using the appropriate version for their consuming application.

Header-based versioning is RECOMMENDED (see section API Version Control); however, it is recognised that some API infrastructure does not readily support header-based versioning, URL-based versioning is a viable alternative, as the version number in the URL should only change when major revisions have been made and the interface has changed substantially without backwards compatibility.💡

For URL-based versioning the URI SHOULD include /vN with the major version (N) and v as a prefix.💡

APIs SHOULD NOT include minor version numbers when using version numbers in the path.💡

Template

/v{version}/

Example

# Get details for provider id 123435 – version 1 of the API

GET https://api.example.govt.nz/v1/providers/12345
Accept: application/json
Host: api.example.govt.nz
# Get details for provider id 123435 – version 2 of the API

GET https://api.example.govt.nz/v2/providers/12345
Accept: application/json
Host: api.example.govt.nz

Namespaces

API Providers may hold multiple responsibilities which can result in overlapping resource naming (for example "supplier" could be used in both pharmacy/contracts and laboratory/contracts).

It is RECOMMENDED that namespaces be used to avoid any ambiguity.💡

The namespace SHOULD be the first noun in the URI and SHOULD reflect the function of government being offered by this API.💡

Namespaces MAY be singular or plural, depending on the situation.💡

Template

/{version}/{namespace}/

Example

/v1/laboratories/

Resources and Sub-resources

Resource names SHOULD be noun-based, and collection resource names SHOULD be plural nouns, e.g. /laboratories in lower case.💡

Resource naming SHOULD be short, simple, and clearly understandable. It SHOULD also be human-guessable, avoiding technical or specialist terms where possible.💡

Sub-resources MUST appear under the resource they relate to, i.e. /resource/id/sub-resource/id💡

Sub-resources SHOULD go no more than three deep i.e. /resource/id/sub-resource/id/sub-sub-resource.💡

If you reach a third level of granularity (sub-sub-resource), it may be worth reviewing your resource construction to see if it is actually a combination of multiple first or second level resources.

The URI references for resources SHOULD consistently use the same path structure to refer to resources. Sub-namespace or sub-folders SHOULD be avoided, to maintain path consistency.💡

This allows application developers to have a predictable experience in case they are building URIs in code.

Template

/{version}/{namespace}/{resource}/{resource-id}/{sub-resource}/{sub-resource-id}

/{version}/{resource}/{resource-id}/{sub-resource}/{sub-resource-id}

Example

<https://api.example.govt.nz/v2/contracts/laboratories/33245/locations/P100782>

<https://api.example.govt.nz/v2/vaccinators/43265/certifications/7281>

Word Separation

Hyphens have traditionally been used as word separators in URLs, as search engines (particularly Google) prefer a hyphen to split words because a hyphen is not a word character (as defined in regular expression language). This has led to hyphens, or kebab-case, being the de facto standard in the interests of readability and Search Engine Optimization (SEO).

Therefore, in order to keep URLs consistently formatted, path and query string parameters SHOULD be lower case with hyphen separators for multiword names.💡

Example

https://api.example.govt.nz/v1/example-service/search?sort-order=asc

Query Arguments

Query arguments should be used to filter a response i.e. modify a returning result set.

The general rule is:

  • If it changes the behaviour of the result set then it SHOULD be a query argument.💡

  • If it changes the behaviour of the API then it SHOULD be in the path.💡

Query arguments are generally used for:

  • Sorting or ordering the result set - e.g. sort-order=ascending or sort-order=descending

  • Pagination - pagination is a query argument because it effectively acts as a filter and limits the result set returned. This is particularly useful with large response data sets.

    When using pagination, the response SHOULD inform the consumer where they can find previous and subsequent result sets using hypermedia as discussed in the HATEOAS section of this document.💡

Example

"_links": [
{
"rel": "next",
"href": "https://api.example.govt.nz/v2.3/transactions?page=3"
},
{
"rel": "prev",
"href": "https://api.example.govt.nz/open-banking-nz/v2.3/transactions?page=1"
},
{
"rel": "self",
"href": "http://api.example.govt.nz/open-banking-nz/v2.3/transactions?page=2"
},
{
"rel": "last",
"href": "http://api.example.govt.nz/open-banking-nz/v2.3/transactions?page=10"
}
],
"meta": {
"totalPages": 10
}
  • Limiting the result set - e.g. by specifying which fields to return. This approach can be complicated and is often a decision based on functionality vs complexity. For example, it may be desirable to be able to filter a result set to a specific set of objects.💡

    Whilst this is possible, it is not a recommended approach. If this kind of flexibility is required in an API it could be a good time to consider the use of Open Data Protocol (OData) or GraphQL.

  • In cases where response filtering is used, providers should ensure that they use a JSON schema by default so that consumers have the ability to understand the entire resource and do not need to query the resource for the message structure.💡

Example

# Get a filtered result set for a vaccinator (REST)
GET https://api.example.govt.nz/v2/vaccinators/33245?fields=firstName,lastName,dateOfBirth
{
"firstName": "Mary",
"lastName": "Contrary",
"dateOfBirth": "12-01-1974",
"_links": [
{
"rel": "self",
"href": "https://api.example.govt.nz/v2/vaccinators/33245?fields=firstName,lastName,dateOfBirth"
}
]
}
# Get a filtered result set for a Patient (GraphQL)
# POST https://api.example.govt.nz/$graphql
query {
Patient(id:"03cb8799-bfbd-40fa-9ea8-96114cf1fec1") {
name {family, given}
birthDate
}
}

{
"data": {
"Patient": {
"name": [
{
"family": "DuBuque",
"given": [
"Calvin"
]
}
],
"birthDate": "2012-10-09"
}
}
}