Making an API Public
Introduction
In this document, we explain both how you make an API public and the considerations to take into account before doing so.
Definitions
Managed Client
A managed client is one that Sentry controls, such as our frontend. In this type of client, we control both which endpoints are used and the attributes used by those endpoints.
Partially Managed Client
A partially-managed client is one that we only partially control, since we do not control the version of the client that is running for our users; for example, our Sentry CLI. While we control the endpoints the CLI uses, once the CLI is installed and running in a customer's implementation, we don't control the version of the client a user runs.
External Client
An external client is a client that Sentry does not control; for example, a script written by a customer run in their repository, or an integration written using the Integration Platform. For this type of client, we have no control over the endpoints they choose to use, nor can we require that they immediately update their endpoint.
Private API
A private API is an undocumented API. Sentry can make breaking changes to this type of API, and no one will blink an eye.
Public API
Our public API is documented at https://docs.sentry.io/api/.
Private Attributes in a Public Endpoint
You can have private attributes within a public endpoint.
As an example: https://docs.sentry.io/api/teams/retrieve-a-team/. The response has multiple attributes:
{
"id": "2",
"slug": "the-interstellar-jurisdiction"
...
}
Let's say we also return a nickname
for a team. If what is returned is not documented as a
response in our public documentation, that attribute is a private attribute within a public
endpoint.
DRF (Django REST Framework)
Django REST framework is a powerful and flexible toolkit for building Web APIs. Sentry APIs are built using DRF.
API Versioning
Currently, Sentry's API is v0
and considered to be in draft phase. While we don't expect our
public endpoints to change greatly, keep in mind that our API is still under development.
Making an endpoint public
When you make an endpoint public, you're committing to always returning at least the set of defined attributes when that endpoint is called. As a result, the attribute(s) cannot be removed from an endpoint after they are public. Additionally, the type of the attribute cannot be changed once the attribute is public. The reason the attribute and its type cannot be changed is because both external and managed clients are relying on the public attribute to be returned from that endpoint.
You can add attributes to a public endpoint.
When to make an API public?
Making an API public offers considerable benefits. A public API allows our customers to incorporate Sentry into their workflows. It also gives Sentry more visibility in our customers' organizations ("Woah that's cool data, where'd it come from?"..."Sentry!"). Remember, however, making an API public means that once an endpoint is public, you can only add to it: You cannot make any breaking changes.
As a guide, use these questions:
- Is the feature for which you're making the public endpoint stable?
- Will the API change substantially in the future?
If your answers are Yes and No, you're in business - make the endpoint public. Head over to the public API checklist and ensure that your endpoint conforms to the checklist.
How to make an endpoint public?
We use Open API Spec 3 and the drf-spectacular library to document our APIs.
API Documentation consists of:
- Declaring the Endpoint Public
- Sidebar Tab
- Method Decorator
- Title
- Path and Query Parameters
- Request Schema
- Success Responses
- Error Responses
- Sample Response Body
- Endpoint Description
We utilize the drf-spectacular's
@extend_schema
decorator to perform the documentation. The sections below outline each step to use this decorator.
1. Declaring the Endpoint Public
Declare the endpoint public by setting the public
attribute with the appropriate HTTP request
methods.
class OrganizationTeamsEndpoint(...):
public = {"GET", "POST"}
2. Sidebar Tab
Specify the endpoint's sidebar tab by using the
@extend_schema
decorator on the endpoint class. You can see the current list of tags or add tags
here. In the example
below the endpoint
is tagged in the Teams
sidebar tab.
from drf_spectacular.utils import extend_schema
@extend_schema(tags=["Teams"])
class OrganizationTeamsEndpoint(...):
public = {"GET", "POST"}
3. Method Decorator
We utilize another @extend_schema
decorator on the endpoint function to perform the majority of
the documentation. The code below provides an example of a fully documented endpoint.
@extend_schema(tags=["Teams"])
class OrganizationTeamsEndpoint(...):
public = {"GET", "POST"}
@extend_schema(
operation_id="Create a New Team",
parameters=[
GlobalParams.ORG_SLUG,
GlobalParams.name("The name for the team.", required=True),
GlobalParams.slug("Optional slug for the team."),
],
request=TeamPostSerializer,
responses={
201: TeamSerializer,
400: RESPONSE_BAD_REQUEST,
403: RESPONSE_FORBIDDEN,
404: OpenApiResponse(description="A team with this slug already exists."),
},
examples=TeamExamples.CREATE_TEAM,
)
def post(self, request, organization, **kwargs):
"""
Create a new team bound to an organization.
"""
Title
Specify an operation_id
that will be shown as the title of the endpoint's page in the
documentation.
@extend_schema(
operation_id="Create a New Team",
...
)
Path and Query Parameters
Specify the parameters using a list of parameters and serializers.
- You can find existing parameters in this
file. Note
that the
description
field inOpenApiParameter
populates the parameter's description in the documentation. - DRF serializers conveniently translate into query parameters. See here for an example of this.
parameters=[
GlobalParams.ORG_SLUG,
GlobalParams.name("The name for the team.", required=True),
GlobalParams.slug("Optional slug for the team."),
],
Request Schema
Specify the request serializer.
- Request serializer can generally just be the DRF serializer of endpoint itself. If you need more customization or the endpoint doesn't use a serializer, you can use drf-spectacular's inline_serializer to create a one-off serializer. See here for an example of this.
- If the endpoint has no request body, you can either omit the
request
field or set it toNone
.
request=TeamPostSerializer,
Success Responses
Specify the return type of success responses, which are used to generate the response schema and validate the sample response body. There are three ways to do this:
If the success response is a single object instead of a list, you can pass a DRF serializer as the response. In order for this serializer to generate a schema, its
serialize
method must be typed to return a TypedDict.For example, this sample code has the
200
status code returning anOrganizationMemberSCIMSerializer
. It'sserialize
method is typed to return anOrganizationMemberSCIMSerializerResponse
TypedDict which specifies the typing of the response body.Note that in order indicate optional fields that may or may not be set in the response, you must inherit from a TypedDict with
total=False
in it's class header. Optional fields should be grouped together and cannot exist in the same class as required fields. See here for an example of this.Copiedclass ExampleResponseOptional(TypedDict, total=False): optionalStringField: str
For fields that may be null, use
Optional
followed by the field's type.Copiedclass ExampleResponse(TypedDict, ExampleResponseOptional): potentiallyNullStringField: Optional[str]
Note that a field can be both optional and null.
To return a list of objects or for additional customization, use the inline_sentry_response_serializer function.
Copiedfrom sentry.apidocs.utils import inline_sentry_response_serializer @extend_schema( responses={ 200: inline_sentry_response_serializer( "ListOrgTeamResponse", List[TeamSerializerResponse] ), } )
Note that we HIGHLY recommend using the first method when your endpoint returns a single object and utilizes an existing serializer. The first method ensures the documentation stays up-to-date as the endpoint evolves, and provides a higher quality API experience for our users.
You can also provide OpenAPI JSON if you are running into issues, although we recommend avoiding this if possible.
Error Responses
Specify error responses using the existing OpenApiResponse
constants in this
file. You can
also define your own for more detailed messages like the example below.
responses={
201: TeamSerializer,
400: RESPONSE_BAD_REQUEST,
403: RESPONSE_FORBIDDEN,
404: OpenApiResponse(description="A team with this slug already exists."),
},
Sample Response Body
Specify the endpoint's sample response using the examples
field. We keep all our examples in this
folder sorted by
sidebar tags.
@extend_schema(
...
examples=TeamExamples.CREATE_TEAM,
)
from drf_spectacular.types import OpenApiExample
class TeamExamples:
CREATE_TEAM = [
OpenApiExample(
# description of example, not used for anything
"Create a new team",
# actual response body
value={"slug": "my-team", "name": "My Team"},
# the status code(s) this example applies to
status_codes=["201"],
# You MUST INCLUDE this for all examples
response_only=True,
)
]
- Note that the statment
response_only=True
is required for all examples. - We currently only support showing one response example per endpoint.
4. Endpoint Description
Specify the endpoint description in the documentation using the endpoint's docstring.
def post(self, request, organization, **kwargs):
"""
Create a new team bound to an organization.
"""
Caveats
If the endpoint you're modifying had previous JSON documentation, you must delete the old documentation path in this file and its corresponding JSON build in this folder.
Additionally if there are multiple request types in the same endpoint using the old JSON documentation, you must update both of them in the same PR. Updating only one request and deleting the old documentation will cause all other requests to disappear from the docs.
Building and Testing Locally
Commands:
make test-api-docs
builds the OpenAPI JSON, validates the schema for all examples, and runs all API docs tests.make build-api-docs
builds the OpenAPI JSON. The build will fail if there are any warnings.make diff-api-docs
produces a diff of your local OpenAPI JSON with production.make watch-api-docs
autmatoically rebuilds the OpenAPI JSON on change.
To see your changes in the docs locally:
In sentry
:
Use
make watch-api-docs
to continuously build the intermediate assettests/apidocs/openapi-derefed.json
locally.Copy the full path to
{YOUR_SYSTEM_FOLDER}/tests/apidocs/openapi-derefed.json
,e.g.
/Users/yourname/code/sentry/tests/apidocs/openapi-derefed.json
.
In sentry-docs
:
Run
OPENAPI_LOCAL_PATH=<COPIED_FULL_PATH> DISABLE_THUMBNAILS=1 yarn start
and substitute<COPIED_FULL_PATH>
with the path to your local openapi-derefed.json.Unfortunately changes do not automatically reflect in your local server, so you will need to rerun this commmand on every change. We hope to add this feature in the future.
See here for detailed doc build instructions.
When you open the pull request, please add a screenshot of the page or pages you're adding.
Build Process
The openapi-diff test will fail when CI runs on your pull request, this is expected and meant to highlight the diff. It is not required to merge.
Once you make changes to an endpoint and merge the change into Sentry, a series of GitHub Actions will be triggered to make your changes automatically go live:
- The
openapi
workflow insentry
updates the schema in sentry-api-schema with the OpenAPI build artifact. - The
cascade-to-sentry-docs
workflow insentry-api-schema
reacts to the push tomain
in (1) by triggering thebump-api-schema-sha
workflow insentry-docs
. - The
bump-api-schema-sha
workflow insentry-docs
fetches the latest commit SHA fromsentry-api-schema
and writes it into the correct file, then makes and merges a PR insentry-docs
, which kicks off a deploy via Vercel to https://docs.sentry.io/api/.
Requesting an API to be public
Want an endpoint to be public?
Look at the issues on sentry with the Component: API
label.
If a request has already been made to make the endpoint public, give it a thumbs up. If not, create
a feature request on Sentry
and add the Component: API
label.
The team responsible for the endpoint will review the stability of the endpoint. If the endpoint will not have breaking changes in future, they can determine whether to make it public.
FAQs
When should an attribute be required
?
An attribute is required
if it will always be returned by the API.
What does it mean when a response doesn't have a schema?
Some endpoints have no response schema. This means that while the endpoint is public, the attributes within that endpoint can change at any time. This is a relic from migrating the documentation from our prior approach. Note that, going forward, we recommend new endpoints always provide response schema.
I have a question and it has not been answered.
No problem. Send us an email so we can answer your question.
Can customers use private endpoints?
Yes, if they wish. However private endpoints are liable to change without notice at any time, potentially breaking the customer's code. Please be careful using them.