Publishing an API

Introduction

In this document, we explain when and how to publish API documentation to public Sentry docs.

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.

Public APIs

A public API is an API that is stable and well documented at https://docs.sentry.io/api/. 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?

Should I publish my API? The answer in most cases is Yes. As a developer facing company it is critical to make Sentry features available for developers to use in their various automations. Do not try to predict if an automation finds the API useful because we can't predict all the automations our clients might want to build. With that being said, there are 3 exceptions where we don't publish APIs:

  1. Experimental APIs: APIs that are not fully launched or will go through multiple iterations in a short time that could cause breaking changes. These are expected to be published or removed after a while.
  2. Private APIs: APIs that are controlling a UI feature in Sentry, for example if a button is visible.
  3. APIs in getsentry: If you're a Sentry engineer and building APIs in getsentry repo, know that they won't be published. This is the expected behavior for all the getsentry APIs, but if you have a case that you think it should be published let owners-api know and we can help with that.

Before Publishing

Remember, making an API public means that you can only add to it: You cannot make any breaking changes.

As a guide, use these questions:

  1. Is the feature for which you're making the public endpoint stable?
  2. 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.

Publishing an API

We use Open API Spec 3 and the drf-spectacular library to document our APIs.

API Documentation consists of:

  1. Declaring Owner for the Endpoint
  2. Setting Publish Status on Endpoint
  3. Sidebar Tab
  4. Endpoint Description
  5. Method Decorator
    • Title
    • Path and Query Parameters
    • Request Schema
    • Success Responses
    • Error Responses
    • Sample Response Body

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 Owner for the Endpoint

Declare an owner for the endpoint. This would be the team at Sentry responsible to maintain and make publishing decisions about the endpoint methods.

Copied
class OrganizationTeamsEndpoint(...):
    owner = ApiOwner.ENTERPRISE

2. Setting Publish Status on Endpoint

Declare endpoint methods public by adding them to the publish_status with PUBLIC value.

Copied
class OrganizationTeamsEndpoint(...):
    owner = ApiOwner.ENTERPRISE
    publish_status = {
        'GET': ApiPublishStatus.PUBLIC, 
        'POST': ApiPublishStatus.PUBLIC,
        'PUT': ApiPublishStatus.EXPERIMENTAL,
    }

3. 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.

Copied
from drf_spectacular.utils import extend_schema

@extend_schema(tags=["Teams"])
class OrganizationTeamsEndpoint(...):
    owner = ApiOwner.ENTERPRISE
    publish_status = {
        'GET': ApiPublishStatus.PUBLIC, 
        'POST': ApiPublishStatus.PUBLIC,
        'PUT': ApiPublishStatus.EXPERIMENTAL,
    }

4. Endpoint Description

Specify the endpoint description in the documentation using the endpoint's docstring. Please make sure to include a description for the resources involved as well if they are not clear. For example, team and organization here don't require description but other words like external-issue or integration are vague and need description.

Copied
def post(self, request, organization, **kwargs):
    """
    Create a new team bound to an organization.
    """

5. Method Decorator

We utilize another @extend_schema decorator on the endpoint method to perform the majority of the documentation. The code below provides an example of a fully documented post API.

Copied
@extend_schema(tags=["Teams"])
class OrganizationTeamsEndpoint(...):
    owner = ApiOwner.ENTERPRISE
    publish_status = {
        'GET': ApiPublishStatus.PUBLIC, 
        'POST': ApiPublishStatus.PUBLIC,
        'PUT': ApiPublishStatus.EXPERIMENTAL,
    }

    @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.
        """

Here's description of each field in the method decorator:

  • operation_id: will be shown as the title of the endpoint's page in the documentation.
  • parameters: is a list of path and query parameters, and whether or not they are required.
    • You can find existing parameters in this file. Note that the description field in OpenApiParameter populates the parameter's description in the documentation.
    • DRF serializers conveniently translate into query parameters. See here for an example of this.
  • request: is the request serializer that can generally just be the DRF serializer of the 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 to None.
  • responses: includes types of response returned for all possible HTTP response cases both in case of success and failure. You can learn more about each in the next section.
  • examples: specifies the endpoint's sample response. We keep all our examples in this folder sorted by sidebar tags.
    • Note that the statement response_only=True is required for all examples.
    • We currently only support showing one response example per endpoint but support for multiple examples is on the way. So, feel free to add multiple examples here.
Copied
@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,
      )
  ]

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:

  1. 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 an OrganizationMemberSCIMSerializer. It's serialize method is typed to return an OrganizationMemberSCIMSerializerResponse 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.

    Copied
    class ExampleResponseOptional(TypedDict, total=False):
        optionalStringField: str

    For fields that may be null, use Optional followed by the field's type.

    Copied
    class ExampleResponse(TypedDict, ExampleResponseOptional):
        potentiallyNullStringField: Optional[str]

    Note that a field can be both optional and null.

  2. To return a list of objects or for additional customization, use the inline_sentry_response_serializer function.

    Copied
    from 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.

  3. 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.

Copied
responses={
    201: TeamSerializer,
    400: RESPONSE_BAD_REQUEST,
    403: RESPONSE_FORBIDDEN,
    404: OpenApiResponse(description="A team with this slug already exists."),
},

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:

Copied
{
  "id": "2",
  "slug": "the-interstellar-jurisdiction"
  ...
}

Let's say we also return a nickname for a team. If the data returned is not documented as a response in our public documentation, that attribute is a private attribute within a public endpoint.

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:

  1. Use make watch-api-docs to continuously build the intermediate asset tests/apidocs/openapi-derefed.json locally.

  2. 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:

  1. 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:

  1. The openapi workflow in sentry updates the schema in sentry-api-schema with the OpenAPI build artifact.
  2. The cascade-to-sentry-docs workflow in sentry-api-schema reacts to the push to main in (1) by triggering the bump-api-schema-sha workflow in sentry-docs.
  3. The bump-api-schema-sha workflow in sentry-docs fetches the latest commit SHA from sentry-api-schema and writes it into the correct file, then makes and merges a PR in sentry-docs, which kicks off a deploy via Vercel to https://docs.sentry.io/api/.

Requesting an API to be public

Are you a Sentry user who wants 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.

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.

I have a question and it has not been answered.

No problem. Send us an email so we can answer your question.

You can edit this page on GitHub.