|
| 1 | +--- |
| 2 | +title: Designing a New API |
| 3 | +sidebar_order: 2 |
| 4 | +--- |
| 5 | + |
| 6 | +[Django REST framework](https://www.django-rest-framework.org/)(DRF) is a powerful and flexible toolkit |
| 7 | +for building Web APIs. Sentry APIs are built using DRF. Here are some considerations to make when designing APIs at Sentry. |
| 8 | + |
| 9 | +## URL Patterns |
| 10 | +The API's URL is what developers use to call the endpoint so it’s important that it is meaningful and clear. |
| 11 | + |
| 12 | +### Don't Exceed 3-level Nesting |
| 13 | +Nested resources format like `api/0/organizations/\{org}/projects/` is recommended over `api/0/projects/` for readability because it gives user an understanding of resource hierarchy. However, nesting can make URLs too long and hard to use. Sentry uses 3-level nesting as a hybrid solution. |
| 14 | + |
| 15 | +Here are some possible urls for values with this resource hierarchy: organization -> project -> tag -> value |
| 16 | +- 👍 `/projects/\{organization_slug}/\{project_slug}/tags/\{tag_id}/values` |
| 17 | +- 👎 `/organizations/\{organization_slug}/projects/\{project_slug}/tags/\{tag_id}/values/` |
| 18 | +- 👎 `/values/` |
| 19 | + |
| 20 | +In the above example we flattened `projects`. The table below shows the existing flattened collections which works out with our existing APIs. |
| 21 | + |
| 22 | +| First collection in URL | When to use | Parent | Identifier | Example | |
| 23 | +| --- | --------- | ----- | --- | --- | |
| 24 | +| organizations | When the resource cannot be attached to any other collection below parent like Project | N/A - always comes as first collection | \{organization_slug} | [Create a New Team](https://docs.sentry.io/api/teams/create-a-new-team/) | |
| 25 | +| teams | When the resource is under a specific team in the hierarchy | organizations | \{organization_slug}/ \{team_slug} | [Retreive Team Stats](https://docs.sentry.io/api/teams/retrieve-event-counts-for-a-team/) | |
| 26 | +| projects | When the resource is under a specific project in the hierarchy but not under an issue | organizations | \{organization_slug}/ \{project_slug} | [Create a New Client Key](https://docs.sentry.io/api/projects/create-a-new-client-key/)| |
| 27 | +| issues | When the resource is under a specific issue in the hierarchy | projects | \{issue_id} | [List an Issue's Events](https://docs.sentry.io/api/events/list-an-issues-events/)| |
| 28 | +| sentry-app-installations | When the resource is mapped to a specific integration | organizations | \{integration_slug} | [Delete an External Issue](https://docs.sentry.io/api/integration/delete-an-external-issue/)| |
| 29 | + |
| 30 | +Here are some additional examples: |
| 31 | +- 👍 `/organizations/\{organization_slug}/projects/` |
| 32 | +- 👍 `/projects/\{organization_slug}/\{project_slug}/issues/` |
| 33 | +- 👎 `/projects/` |
| 34 | + |
| 35 | +<Alert title="Note" level="info"> |
| 36 | + |
| 37 | +Hierarchy here does not necessarily mean that one collection belongs to a parent collection. For example: |
| 38 | +- `projects/\{project_identifier}/teams/` refers to the **teams** that have been added to specific project |
| 39 | +- `teams/\{team_identifier}/projects/` refers to the **projects** a specific team has been added to |
| 40 | + |
| 41 | +</Alert> |
| 42 | + |
| 43 | +### Naming Guidance |
| 44 | +- Collection names should be lowercase and hyphenated, e.g. `commit-files`. |
| 45 | +- Collection names must be plural. Avoid using uncountable words because the user can’t know whether the GET returns one item or a list. |
| 46 | +- Query params and body params should be `camelBacked`. eg. `userId` or `dateCreated`. |
| 47 | +- For sorting and filtering, stick with the common param names: `sortBy` (e.g. `sortBy=-dateCreated`), `orderBy` (either `asc` or `desc`), `groupBy`, `limit` |
| 48 | +- Path params should be `snake_case` |
| 49 | + |
| 50 | +## Functionality |
| 51 | +Each API should be **stateless**, have a clear purpose, and do one specific thing. To achieve that, stick with the standard methods listed below. If your API needs to be more complicated, work with [owners-api](https://github.com/orgs/getsentry/teams/owners-api) on how to create it. |
| 52 | + |
| 53 | +- 👍 An API that updates project settings: PATCH for updating a field or PUT for updating settings |
| 54 | +- 👎 An API that creates a project, creates a team, and creates alerts for that team about that project |
| 55 | + |
| 56 | +| Functionality | HTTP Method | Response Object | Example | |
| 57 | +| --- | ---- | ---- | ---- | |
| 58 | +| Create | POST | Serialized created resource | [Create a Project](https://github.com/getsentry/sentry/blob/756bda4419cfaf28b2e351278a5c4c1665082eba/src/sentry/api/endpoints/team_projects.py#L156) | |
| 59 | +| Update | PUT or PATCH | Serialized updated resource | [Update Project Settings](https://github.com/getsentry/sentry/blob/756bda4419cfaf28b2e351278a5c4c1665082eba/src/sentry/api/endpoints/project_details.py#L474) | |
| 60 | +| Get | GET | Serialized single resource | [Retrieve a Project](https://github.com/getsentry/sentry/blob/756bda4419cfaf28b2e351278a5c4c1665082eba/src/sentry/api/endpoints/project_details.py#L415) | |
| 61 | +| Delete | DELETE | None | [Delete a Project](https://github.com/getsentry/sentry/blob/756bda4419cfaf28b2e351278a5c4c1665082eba/src/sentry/api/endpoints/project_details.py#L840) | |
| 62 | +| List | GET | List of multiple serialized resources | [List All the Projects in an Organization](https://github.com/getsentry/sentry/blob/756bda4419cfaf28b2e351278a5c4c1665082eba/src/sentry/api/endpoints/organization_projects.py#L49) | |
| 63 | +| Batch Get | GET | List of serialized resources | Get project details for specific project ids | |
| 64 | +| Batch Create | POST | List of serialized created resources | Create multiple projects with the same settings | |
| 65 | +| Batch Update | PUT | List of serialized updated resources | [Update a list of issues](https://github.com/getsentry/sentry/blob/ea14f740c78b8df68281ffad6a3bf3709ed3d5b5/src/sentry/api/endpoints/organization_group_index.py#L379) | |
| 66 | +| Batch Delete | DELETE | None | [Delete a list of issues](https://github.com/getsentry/sentry/blob/ea14f740c78b8df68281ffad6a3bf3709ed3d5b5/src/sentry/api/endpoints/organization_group_index.py#L467)| |
| 67 | + |
| 68 | +Here are some examples of how to use standard methods to represent complex tasks: |
| 69 | +- Get count of a resource |
| 70 | + - Count is part of the `List` API and is provided in header X-Total-Count param |
| 71 | +- Get latest of a resource |
| 72 | + - Order and filtering should happen as part of list api query parameters. Here’s a [good read](https://www.moesif.com/blog/technical/api-design/REST-API-Design-Filtering-Sorting-and-Pagination/). |
| 73 | + - 👎 `/api/0/issues/\{issue_id}/events/latest/` |
| 74 | + - 👍 `/api/0/issues/\{issue_id}/events?orderBy=-date,limit=1`, `-` for descending |
| 75 | + |
| 76 | +### Batch vs. Single Resource Methods |
| 77 | +Here are some notes that can help you decide between similar methods. We use *Get* here as an example but the same applies to all the other methods in the parenthesis. |
| 78 | +- **Get (Update, Delete)**: Use get on the `\{resource}DetailsEndpoint` to retrieve a resource. For example, `ProjectDetailsEndpoint`. |
| 79 | +- **List (Create, Batch Create, Batch Update, Batch Delete)**: Use get on the `\{resource-parent}\{resource}Endpoint` to retreive all resources that belong to that parent. For example `TeamProjectsEndpoint`. |
| 80 | +- **Batch Get (Batch Create, Batch Update, Batch Delete)**: Use get on the `\{root-parent}\{resource}Endpoint`. The difference between `Batch` and `List` is that batch usually includes a list of `ids` as query parameter and returns details about those ids. This list does not necessarily belong to one parent. For example, we can't retrieve two projects that belong to two different teams in the above example and in that case we use the get method in the root resource, in this case `OrganizationProjectsEndpoint`. |
| 81 | + |
| 82 | +## Response Object |
| 83 | +Each response object returned from an API should be a serialized version of the Django model associated with the resource. You can see all the existing serializers [here](https://github.com/getsentry/sentry/tree/master/src/sentry/api/serializers/models). |
| 84 | + |
| 85 | + <Alert title="Note" level="info"> |
| 86 | + |
| 87 | + Some models might have different serializers based on use case. For example, `Project` can be serialized into `DetailedProjectSerializer` or `ProjectSerializer`. Decide which one to use based on your use case and API scope but **DO NOT RETURN CUSTOM OBJECTS** like \{`slug: project_slug, platform: project_platform`}. We want the API responses to be uniform and useable in multiple automations without adding extra complication to the external developers' code. |
| 88 | + |
| 89 | + |
| 90 | + </Alert> |
0 commit comments