Documentation¶
Requirement
- API teams must add their API to the CODE VA catalog.
- API teams must document their API using OpenAPI Specification version 3.0.x.
- OpenAPI docs must be valid YAML or JSON so they are machine-readable.
Guidance
- Use industry tools for your language and technical stack to automate the generation and upkeep of your OAS.
CODE VA¶
Your API must be added to the CODE VA catalog, VA's Catalog of Developer Essentials. This aids others within VA to be able to discover your API. This is helpful even if your API was written specifically for just one consumer. Documenting the intent of use for your API will make others within VA aware of your API’s existing capabilities.
OpenAPI specification¶
OpenAPI Specification (OAS) is the predominant API documentation industry standard for describing HTTP APIs. This standard allows people to understand how the API works, generate client code, and what to expect when using the API. All teams at VA must document their APIs using the OpenAPI Specification version 3.0.x.
Documentation for humans and computers¶
An OAS serves as documentation for human consumers to read when evaluating or using an API, and for machines to use when monitoring and verifying consistency between the API’s specification and its behavior.
OAS documents must pass validation in tools such as the Swagger Editor.
Access to your API's documentation is best when available from a URL that your API hosts. This allows machines and humans to consume this information. For example, if your service is called "rx" then, https://{hostname}/rx/{version}/openapi.json
would be a good location to host your API documentation. Using openapi.json
or openapi.yaml
follows the Open API Specification recommendation.
It is highly recommended to use tools such as Springdoc, or Express OAS Generator to simplify the creation and maintenance of the OAS. You are free to use any tool that works best for your code, language, and situation.
See the guidance under Production Management for keeping the documentation in sync with the API releases.
Example OAS document¶
Below is an example OAS doc for a fictitious 'Rx' API. Click on the circular buttons labeled with a '+' to view code annotations.
---
openapi: 3.0.1 # (1)
info: # (15)
title: Rx
description: An example 'Rx' API that follows the VA API Standards.
contact:
name: support@va.gov #(20)
version: 0.0.0 #(2)
servers: # (3)
- url: https://dev-api.va.gov/services/rx/{version}
description: Development environment
variables:
version:
default: v0
- url: https://staging-api.va.gov/services/rx/{version}
description: Staging environment
variables:
version:
default: v0
- url: https://sandbox-api.va.gov/services/rx/{version}
description: Sandbox environment
variables:
version:
default: v0
- url: https://api.va.gov/services/rx/{version}
description: Production environment
variables:
version:
default: v0
paths:
/pharmacies:
get:
tags:
- pharmacy
summary: Returns a list of VA facilities with pharmacies. # (16)
description: Returns a paginated list of all VA facilities that provide
pharmacological services.
operationId: getPharmacies
responses:
"200":
description: Returns a paginated list of VA facilities with pharmacies, and an empty list if none are found for the given criteria.
content:
application/json:
schema: # (4)
$ref: "#/components/schemas/PharmacyList"
"500": # (5)
$ref: "#/components/responses/ErrorInternalServerError"
"502": # (6)
$ref: "#/components/responses/ErrorInternalServerError"
"503": # (7)
$ref: "#/components/responses/ErrorServiceUnavailable"
security: # (8)
- {}
/prescriptions:
get:
tags:
- prescription
summary: Returns a list of a Veteran's prescriptions
description: Given a Veteran's ICN, return a list of their
prescriptions.
operationId: getPrescriptions
parameters: # (17)
- in: query
name: icn
description: MPI ICN
required: true
schema:
maxLength: 17
minLength: 17
pattern: ^\d{10}V\d{6}$
type: string
example: [1012667145V762142]
responses:
"200":
description: Return a list of the Veteran's prescriptions and an empty list if none are found.
content:
application/json:
schema:
$ref: "#/components/schemas/PrescriptionList"
"401": # (18)
$ref: "#/components/responses/ErrorUnauthorized"
"403":
$ref: "#/components/responses/ErrorForbidden"
"404":
$ref: "#/components/responses/ErrorNotFound"
"422":
description: Unprocessable Entity
content:
application/json:
schema:
type: object
example:
- errors:
- status: 422
title: Unprocessable Entity
detail: Entity given was unprocessable.
"500":
$ref: "#/components/responses/ErrorInternalServerError"
"502":
$ref: "#/components/responses/ErrorBadGateway"
"503":
$ref: "#/components/responses/ErrorServiceUnavailable"
security: # (12)
- production:
- prescription.read # (19)
components: # (9)
securitySchemes:
production:
type: oauth2
description: This API uses OAuth2 with the authorization code grant flow.
flows:
authorizationCode:
authorizationUrl: https://api.va.gov/oauth2/authorization
tokenUrl: https://api.va.gov/oauth2/token
scopes:
prescription.read: Retrieve prescription data
sandbox:
type: oauth2
description: This API uses OAuth 2 with the authorization code grant flow.
flows:
authorizationCode:
authorizationUrl: https://sandbox-api.va.gov/oauth2/authorization
tokenUrl: https://sandbox-api.va.gov/oauth2/token
scopes:
prescription.read: Retrieve prescription data
responses: # (10)
ErrorUnauthorized:
description: Unauthorized
content:
application/json:
schema:
type: object
example:
- errors:
- status: 401
title: Unauthorized
detail: Invalid credentials. The access token has expired.
ErrorForbidden:
description: Forbidden
content:
application/json:
schema:
type: object
example:
- errors:
- status: 403
title: Forbidden
detail: You do not have access to the requested resource.
ErrorNotFound:
description: Not Found
content:
application/json:
schema:
type: object
example:
- errors:
- status: 404
title: Not Found
detail: The requested resource could not be found.
ErrorInternalServerError:
description: Internal Server Error
content:
application/json:
schema:
type: object
example:
- errors:
- status: 500
title: Internal Server Error
detail: An internal API error occurred.
ErrorBadGateway:
description: Bad Gateway
content:
application/json:
schema:
type: object
example:
- errors:
- status: 502
title: Bad Gateway
detail: An upstream service the API depends on returned an error.
ErrorServiceUnavailable:
description: Service Unavailable
content:
application/json:
schema:
type: object
example:
- errors:
- status: 503
detail: An upstream service is unavailable.
schemas:
PharmacyList:
type: object
required:
- data
properties:
data:
type: array
items:
$ref: "#/components/schemas/Pharmacy" # (11)
Pharmacy:
type: object
required:
- type
- id
- attributes
properties:
type:
type: string
example: ["Pharmacy"]
id:
type: string
example: ["6e976911-2707-4018-a6db-0d1342326379"]
attributes:
type: object
required:
- id
- name
- city
- state
- cerner
- clinics
properties:
id:
type: string
example: ["358"]
name:
type: string
example: ["Cheyenne VA Medical Center Pharmacy"]
city:
type: string
example: ["Cheyenne"]
state:
$ref: "#/components/schemas/State"
PrescriptionList:
type: object
required:
- data
properties:
data:
type: array
items:
$ref: "#/components/schemas/Prescription"
Prescription:
type: object
required:
- type
- id
- attributes
properties:
type:
type: string
example: ["Prescription"]
id:
type: string
example: ["db8a52f0-b3d2-4cc9-bcab-7053d88737d5"]
attributes:
type: object
required:
- productNumber
- referenceDrug
- brandName
- activeIngredients
- referenceStandard
- dosageForm
- route
properties:
productNumber:
type: string
example: ["001"]
referenceDrug:
type: boolean
example: [false]
brandName:
type: string
example: ["FAMOTIDINE PRESERVATIVE FREE"]
activeIngredients:
type: object
required:
- name
- strength
properties:
name:
type: string
example: ["FAMOTIDINE"]
strength:
type: string
example: ["10MG/ML"]
referenceStandard:
type: boolean
example: ["false"]
dosageForm:
type: string
enum: # (13)
- INJECTABLE
- TABLET
example: ["TABLET"]
route:
type: string
enum:
- INJECTION
- ORAL
example: ["ORAL"]
State: # (14)
type: string
enum:
- AK
- AL
- AR
- AZ
- CA
- CO
- CT
- DE
- FL
- GA
- HI
- IA
- ID
- IL
- IN
- KS
- KY
- LA
- MA
- MD
- ME
- MI
- MN
- MO
- MS
- MT
- NC
- ND
- NE
- NH
- NJ
- NM
- NV
- NY
- OH
- OK
- OR
- PA
- RI
- SC
- SD
- TN
- TX
- UT
- VA
- VT
- WA
- WI
- WV
- WY
example: ["WY"]
- OpenAPI Spec 3.0.x is the required OAS specification version when writing your API specification.
- This version represents the version of the API and should be updated as releases are delivered.
- The
servers
section should reflect the location of your API hosted domain and API base path. All endpoints should then be relative to this URL. For example, for servers you might have one for each of the environments such as: dev, staging, sandbox, production. In this example Rx API for the VA, the servers property has been filled out for all 4 environments. It is recommended to NOT provide IP addresses here. - Using
$ref
properties (references) to link to response body schema definitions helps keep your endpoint definitions short. - Errors happen. We require that all APIs let consumers know the error schema your API will return should your API encounter an unexpected error.
- Many VA APIs rely on one or more upstream services for their data. If your
API could throw a
500
if an upstream service returns an error, consider returning a502
error instead, so you and the consumer know a dependency rather than the API is causing the issue. - If your API could throw a
500
if an upstream service is down, consider returning a503
error instead, so both you and the consumer know a dependency rather than the API is causing the issue. - All endpoints must have a security property, if one is not set globally for all endpoints. In this contrived example, the endpoint does not require authentication. To handle this, the
security
tag is added with an empty object. This informs the consumer of the endpoint that security is optional. - Often, multiple API operations have some common parameters or return the same
response structure. To avoid code duplication, you can place the common
definitions in the global
components
section and reference them using$ref
. - Responses, and specifically errors, should be the same layout or shape for all
endpoints. Defining them within the
components/responses
can help reduce the size of your path definitions. $refs
can have$refs
. Think of schemas like their database counterparts. Each model and its relations should be a distinct schema.- This endpoint returns a Veteran's prescription history, which qualifies as Personal Health Information (PHI). All endpoints that return PII or PHI must use OAuth.
- Enums should be considered constants. As in 'C' style languages, they should be UPPER_CASE with underscores for spaces.
- Components don't have to be resources. Any data that appears in multiple locations, such as a list of states, can be a component.
- Info tags must include a
title
and adescription
. Thecontact
property should not contain an individual's personal or work email address, instead use a generic contact if available. - Path methods must contain a shorter
summary
and a longerdescription
that explains the purpose and function of each operation. - Requests should use URL or body parameters rather than headers to pass along requisite data unique to that endpoint.
- Protected endpoints must define
401
Unauthorized and403
Forbidden responses. - The
prescription.read
scope is required for this endpoint so it is listed in the security section. - Contact should be documented within the OAS, and this should be a group email list, Microsoft Teams, or Slack channel where a consumer can send their questions. This must be an actively monitored group email or channel.