Repository navigation
docs: Add service-to-service API standards #894
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
mike-gorman-bitwarden
merged 53 commits into
main
from
docs/service-to-service-api-standards
Oct 28, 2026
Merged
Changes from 44 commits
Commits
Show all changes
53 commits
Select commit
Hold shift + click to select a range
25d4aef
docs: Add service-to-service API standards
mike-gorman-bitwarden 2ddfcf0
docs: Restore examples and details from the draft standards page
mike-gorman-bitwarden a4e73f8
docs: Order standards sidebar alphabetically and group contents
mike-gorman-bitwarden ab4645e
docs: Flatten the standards table of contents
mike-gorman-bitwarden 2e28cfc
docs: Shorten the standards folder name to soa
mike-gorman-bitwarden 6a35bcf
docs: Move server architecture to Service-oriented Architecture with β¦
mike-gorman-bitwarden 107a68b
docs: Align API standards with the Confluence source
mike-gorman-bitwarden 2c55b84
docs: Say API paths "follow the pattern" rather than "be like"
mike-gorman-bitwarden 82d661b
docs: Explain why operations outside CRUD are modeled as actions
mike-gorman-bitwarden 2fa1eb3
docs: Show answers to anticipated questions as FAQ panels
mike-gorman-bitwarden db0bf7f
docs: Restore payload shape page with envelope and resource fields seβ¦
mike-gorman-bitwarden cdc76da
docs: Restore API standards feedback incorporated in earlier PRs
mike-gorman-bitwarden e20fa79
docs: Keep API standards under Server Architecture
mike-gorman-bitwarden 9deef72
docs: Rename API standards to APIs and clarify scope
mike-gorman-bitwarden f789188
Update docs/architecture/server/apis/jobs.md
mike-gorman-bitwarden a8f050a
docs: Show nested email addresses in payload shape examples
mike-gorman-bitwarden dc95e26
docs: Add reasons for always including id and type
mike-gorman-bitwarden d11c32f
docs: Require documented defaults for page number and size
mike-gorman-bitwarden ac7f3a0
docs: Default page number to 1
mike-gorman-bitwarden ae424d3
Apply batched suggestions from code review
mike-gorman-bitwarden 5b3ffdb
docs: Explain when it is safe not to page
mike-gorman-bitwarden ce55ad1
Merge remote-tracking branch 'origin/main' into docs/service-to-serviβ¦
mike-gorman-bitwarden 5fb5550
docs: Rewrap payload shape envelope rule to fix lint
mike-gorman-bitwarden 5191cfd
docs: Spell out key derivation function in advanced queries example
mike-gorman-bitwarden 2a913ab
docs: Require bugs, including security issues, to be fixed in place
mike-gorman-bitwarden a079fba
docs: State the audience of the APIs standards
mike-gorman-bitwarden d90e4f3
docs: Write out contractions in the APIs standards
mike-gorman-bitwarden 138a32a
docs: Link ADR-0039 from the APIs index
mike-gorman-bitwarden 28b76ed
docs: Link ADR-0035 and ADR-0036 to the published API standards
mike-gorman-bitwarden 20336c4
docs: Scope the read-only rule and reject version in bulk updates
mike-gorman-bitwarden 375a886
docs: Count returning fewer results as a breaking change
mike-gorman-bitwarden c440d75
docs: Use a cursor before the first result in the cursor paging example
mike-gorman-bitwarden 9dc5649
docs: Say what the partial updates link to the JSON page points at
mike-gorman-bitwarden 6fe9770
docs: Lead the unrecognized input page with its rules
mike-gorman-bitwarden 6b5200d
docs: Lead the APIs index with notation and point each page to it
mike-gorman-bitwarden bded71d
docs: Replace parenthetical, semicolon, and dramatizing asides
mike-gorman-bitwarden 850899a
docs: Move 500 response rules out of the errors note
mike-gorman-bitwarden 9fcc239
docs: Move rationale into numbered notes or attach it with because
mike-gorman-bitwarden 07fcfa0
docs: Model examples on existing Bitwarden data and cite the source
mike-gorman-bitwarden d0a4ec7
docs: Split bulk operations into one page per topic
mike-gorman-bitwarden c870f42
docs: Name date fields with a Date suffix
mike-gorman-bitwarden ff15d1e
docs: Suffix reference fields with Id
mike-gorman-bitwarden 608dfc8
docs: Mark the undefined job resource as an info note
mike-gorman-bitwarden 7ed0d24
Revert "docs: Mark the undefined job resource as an info note"
mike-gorman-bitwarden fb52e2b
docs: Base the advanced query example on PBKDF2 users
mike-gorman-bitwarden 47caafa
docs: Cover hidden resources in bulk error statuses
mike-gorman-bitwarden 41400f9
docs: Require request order only for bulk create results
mike-gorman-bitwarden 65a6270
docs: Pair each per-resource bulk action body with its target id
mike-gorman-bitwarden 3a1f5d9
docs: Point reactive service-to-service work toward messaging
mike-gorman-bitwarden 94a5f62
Merge remote-tracking branch 'origin/main' into docs/service-to-serviβ¦
mike-gorman-bitwarden 3f03637
docs: Say each API is versioned independently of its parent paths
mike-gorman-bitwarden a8672e2
docs: Say the APIs borrow REST vocabulary but are not RESTful
mike-gorman-bitwarden c0f5ba4
Merge remote-tracking branch 'origin/main' into docs/service-to-serviβ¦
mike-gorman-bitwarden File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,6 +43,7 @@ Iterm | |
| Jetpack | ||
| jslib | ||
| jumpcloud | ||
| kebab | ||
| keychain | ||
| keypair | ||
| keyserver | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| label: "APIs" | ||
| position: 1 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| # Acting upon resources | ||
|
|
||
| Notation follows the [APIs index](./index.md#notation). | ||
|
|
||
| Operations that do not fit creating, reading, updating, or deleting a resource are exposed as | ||
| **actions** on the resource. Modeling each operation as a distinct API makes it easy to discover, | ||
| and lets it be authorized independently of the standard update. | ||
|
|
||
| - HTTP verb `MUST` be `POST`. | ||
| - API path `SHOULD` follow the pattern `/api/v1/{resource plural}/{id}/actions/{action}`, with the | ||
| action named in kebab-case. | ||
| - Unlike the CRUD APIs, _actions_ impose no restrictions on the shape of the JSON posted. | ||
| - A field that changes only through actions, such as a status whose transitions follow business | ||
| rules, `MUST` be read-only, so that no update can bypass the action. | ||
|
|
||
| ```http | ||
| POST /api/v1/users/62bed180-1f78-45d4-8a56-c996936a2947/actions/send-email | ||
| Accept: application/json | ||
| Content-Type: application/json | ||
| ``` | ||
|
|
||
| ```json | ||
| { | ||
| "subject": "Hello", | ||
| "body": "World!" | ||
| } | ||
| ``` | ||
|
|
||
| **Success responses** | ||
|
|
||
| Upon success, APIs `SHOULD` return `204` but `MAY` return `200` or `202`. | ||
|
|
||
| | Status | Description | Response | | ||
| | ---------------- | -------------------------- | -------------------------------------------------------------- | | ||
| | `200 OK` | The action was successful. | The latest representation of the resource. | | ||
| | `202 Accepted` | The action is scheduled. | The [job](./jobs.md) that was scheduled to execute the action. | | ||
| | `204 No Content` | The action was successful. | Nothing. | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| # Advanced queries | ||
|
|
||
| Notation follows the [APIs index](./index.md#notation). | ||
|
|
||
| The `filter` query parameters cover simple predicates, but they cannot express arbitrary `AND` and | ||
| `OR` combinations, and a long expression will exceed the practical limit on URL length. APIs that | ||
| need richer queries `MUST` expose a **search** operation that carries the expression in the request | ||
| body. | ||
|
|
||
| - HTTP verb `MUST` be `POST`. | ||
| - API path `MUST` follow the pattern `/api/v1/{resource plural}:search`. | ||
|
|
||
| The expression is written in [JSON Logic](https://jsonlogic.com/) and placed in a top-level `filter` | ||
| field. JSON Logic is adopted for its notation only. Its full operator set includes control flow, | ||
| iteration, and arithmetic, none of which belong in a query, so only the following operators are | ||
| supported: | ||
|
|
||
| | Operator | Meaning | | ||
| | -------------------- | --------------------------------------------------- | | ||
| | `var` | Reference a field of the resource. | | ||
| | `==`, `!=` | Equal, not equal. | | ||
| | `>`, `>=`, `<`, `<=` | Numeric and date comparison. | | ||
| | `in` | Membership in a list, or a substring match. | | ||
| | `and`, `or` | Boolean composition, each taking two or more terms. | | ||
| | `!` | Negation. | | ||
|
|
||
| A request that uses any other operator, or references a field the API does not support, `MUST` be | ||
| rejected with `422`. | ||
|
|
||
| JSON Logic is used rather than OData's `$filter` because: | ||
|
|
||
| 1. OData's `$filter` is a string expression language, so both building and parsing it require string | ||
| handling where JSON is already right there. | ||
| 2. Composing a filter by concatenating strings means quoting and escaping, which is how injection | ||
| bugs happen. Composing a JSON object does not. | ||
| 3. OData brings a great deal more than filtering - metadata documents, `$expand`, batch, its own | ||
| conventions - and we only want the filtering. | ||
|
|
||
| **Example** | ||
|
|
||
| Find premium users whose key derivation function (KDF) iterations are below the current minimum: | ||
|
mzieniukbw marked this conversation as resolved.
Resolved
|
||
|
|
||
| ```http | ||
| POST /api/v1/users:search?page[size]=50&sort=-creationDate | ||
| Accept: application/json | ||
| Content-Type: application/json | ||
| ``` | ||
|
|
||
| ```json | ||
| { | ||
| "filter": { | ||
| "and": [{ "<": [{ "var": "kdfIterations" }, 600000] }, { "==": [{ "var": "premium" }, true] }] | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| The response `MUST` be identical in shape to the equivalent | ||
| [read-many](./reading-resources.md#read-many) request. | ||
|
|
||
| [Learn more about JSON Logic](https://jsonlogic.com/) | ||
|
|
||
| **Success responses** | ||
|
|
||
| Upon success, APIs `MUST` return `200`. | ||
|
|
||
| | Status | Description | Response | | ||
| | -------- | -------------------------- | ----------------------------------------- | | ||
| | `200 OK` | The search was successful. | The matching resources, paged and sorted. | | ||
|
|
||
| The same notation selects resources for [bulk operations](./bulk-operations/targeting.md). | ||
|
|
||
| The `:search` syntax is credited to [Google AIP-136](https://google.aip.dev/136), and the query | ||
| notation to [JSON Logic](https://jsonlogic.com/). | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| # API paths | ||
|
|
||
| Notation follows the [APIs index](./index.md#notation). | ||
|
|
||
| API paths `MUST` use **lowercase "kebab-case"** and conform as follows: | ||
|
|
||
| 1. The first one or more elements of the API path `MUST` be a namespace. By default, the namespace | ||
| `SHOULD` be `api`. | ||
| 2. The next element of the API path `MUST` be the version number starting with `v1`. | ||
| 3. The next element of the API path `MUST` specify the resource or resources it targets. For | ||
| resource-oriented APIs, this element `SHOULD` be the "resource plural", such as `users`. | ||
| [Bulk-processing](./bulk-operations/index.md) APIs are an exception to this rule. | ||
| - APIs that create resources stop here. | ||
| 4. If the API targets a specific resource, as an update or delete does, the next element of the API | ||
| path `SHOULD` be the ID of the resource it targets. | ||
| 5. Additional elements `MAY` be specified to target sub-resources. | ||
|
|
||
| **Examples** | ||
|
|
||
| ```text | ||
| /api/v1/users | ||
| /api/v1/users/123 | ||
| /api/v1/users/123/devices | ||
| ``` | ||
|
|
||
| Paths `SHOULD` be traversable. If `GET /api/v1/users/123/devices/456` returns the details about | ||
| device 456 of user 123, then every parent path would resolve as follows: | ||
|
|
||
| - `GET /api/v1/users/123/devices` should return all devices of user 123. | ||
| - `GET /api/v1/users/123` should return details about user 123. | ||
| - `GET /api/v1/users` should return all users. | ||
|
|
||
| Not every parent path has to exist as a callable API, and a caller is not necessarily authorized to | ||
| call one that does. Traversable paths show how paths fit together, and how they _would_ resolve if | ||
| APIs existed for every parent path. | ||
|
|
||
| :::note | ||
|
|
||
| 2. The version is in the path because it is then visible in logs, traces, routing rules, and curl | ||
| commands, needs no content negotiation to read, and lets two versions coexist behind one host. | ||
| Header and media-type versioning are both defensible but harder to operate. | ||
|
|
||
| ::: | ||
|
|
||
| ## Which identifiers belong in the path | ||
|
|
||
| Every request targets a **subject** and runs inside a **scope**: | ||
|
|
||
| 1. The **subject** is what the request acts on, which is a resource or a collection of resources. It | ||
| `MUST` be named in the path. | ||
| 2. The **scope** is what the request runs inside - the current organization, the current user. It | ||
| `MUST NOT` be named in the path. It travels as context. | ||
|
|
||
| That is the whole of the path rule. A path says what is acted on or fetched. It does not say what a | ||
| call returns: which parts of the scope an operation requires, permits or refuses is declared by the | ||
| operation, and belongs to authentication and authorization, which a forthcoming standard will cover. | ||
|
|
||
| **Examples** | ||
|
|
||
| | Path | Subject | | ||
| | --------------------------- | ------------------------ | | ||
| | `GET /api/v1/users` | users | | ||
| | `GET /api/v1/users/123` | user 123 | | ||
| | `GET /api/v1/users/current` | the user the scope names | | ||
|
|
||
| `current` `SHOULD` be used where the subject is whatever the scope names, rather than repeating an | ||
| identifier the caller has already supplied. | ||
|
|
||
| Callers `SHOULD` send the current organization and the current user on every request where each | ||
| exists. Two services may publish the same path and declare different things about the scope - one | ||
| `GET /api/v1/organizations` requiring a user and returning theirs, another requiring none and | ||
| returning all. Both name the same subject. The path is not what distinguishes them. | ||
|
|
||
| :::note | ||
|
|
||
| 2. The scope stays out of the path because an identifier in a path is something a caller can try | ||
| other values of, and a path that accepts one must prove on every request that this caller may use | ||
| this value. A scope taken from context is established once, where the caller is authenticated. It | ||
| also keeps one path for a resource that can belong to more than one kind of owner. | ||
|
|
||
| ::: | ||
|
|
||
| ## Working across several scopes | ||
|
|
||
| A caller that reaches several organizations does not turn the organization into a subject, nor does | ||
| it necessarily mean one call per organization. Options include: | ||
|
|
||
| - If it is somewhat rare and typically only "a few", then making N calls with a different | ||
| organization in context is probably fine. | ||
| - The API may be enhanced to let the caller specify which organizations to return data for, as in | ||
| `GET /api/v1/users?filter[organization]=123,456,789`. | ||
| - An [advanced query](./advanced-queries.md) may be used where the list travels in the body rather | ||
| than the URL. | ||
|
|
||
| An operation can also be declared so that supplying less scope means more. | ||
|
|
||
| For example, `GET /api/v1/users` called with a user and an organization in context returns every | ||
| user within the specified organization, but that _same_ API called with only a user in context might | ||
| return every user for every organization the user is a member of. Whether it behaves that way is the | ||
| operation's declaration rather than the path's - and the option exists without any of these patterns | ||
| putting the organization in the path. |
40 changes: 40 additions & 0 deletions
40
docs/architecture/server/apis/breaking-changes-and-versioning.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| # Breaking changes and versioning | ||
|
|
||
| Notation follows the [APIs index](./index.md#notation). | ||
|
|
||
| APIs `SHOULD NOT` make **breaking changes**. A breaking change is any change that causes a request | ||
| that is valid today to be rejected tomorrow, or a response that a caller can process today to become | ||
| unprocessable or incomplete. In practice, that means: | ||
|
|
||
| 1. Adding a new, required field. | ||
| 2. Making an optional field required. | ||
| 3. Changing the datatype of a field. | ||
| 4. Adding additional constraints to a field. | ||
| 5. Removing a field from a response. | ||
| 6. Returning fewer results for the same request, such as introducing paging or lowering a default | ||
| page size. | ||
|
|
||
| The verb is `SHOULD NOT` rather than `MUST NOT` because these are service-to-service APIs and we own | ||
| every caller. A team `MAY` make a breaking change in place when it can account for every caller - | ||
| either because the change has been coordinated with them, or because the rejection surfaces | ||
| somewhere the caller or the user can act on it. | ||
|
|
||
| Bugs, however, including security issues, `MUST` be fixed "in place", without creating new versions | ||
| of the API, even if the changes would technically be considered breaking changes. | ||
|
|
||
| Otherwise, if changes need to be made that _would_ be breaking changes, a new version of the API | ||
| `MUST` be created and the old one [deprecated](./deprecation.md). | ||
|
|
||
| Service-to-service APIs are versioned, unlike the existing public API, because without formal | ||
| versioning every change must be additive, which means the shape can never change and every new field | ||
| is optional. Contracts constrained like that get weaker over time, and what we _want_ to express | ||
| eventually cannot be expressed. | ||
|
|
||
| ## Adding values to constrained fields | ||
|
|
||
| Adding a value to a constrained field deserves a second look. It is additive, so it is not a | ||
| breaking change by the definition above, and a caller that treats the field as an open string is | ||
| unaffected. But a generated client that deserializes the field into a closed enumeration will fail | ||
| on a value it has never seen - and it will fail at the client, on a change that looked safe from the | ||
| service. Consider whether the callers of that field are tolerant of unknown values before adding | ||
| one. |
26 changes: 26 additions & 0 deletions
26
docs/architecture/server/apis/bulk-operations/all-or-nothing-and-best-effort.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| # All-or-nothing and best-effort | ||
|
|
||
| Notation follows the [APIs index](../index.md#notation). | ||
|
|
||
| - **All-or-nothing:** no resource changes unless every resource succeeds. If any resource fails, the | ||
| request fails and the response reports every failure, not just the first. | ||
| - **Best-effort:** each resource succeeds or fails on its own. One resource's failure `MUST NOT` | ||
| prevent or undo another's success. | ||
|
|
||
| A service `MAY` support either mode or both. Callers choose with the `atomic` query parameter: | ||
| `atomic=true` for all-or-nothing, `atomic=false` for best-effort. | ||
|
|
||
| - A service that supports both modes `MUST` default to all-or-nothing. | ||
| - Every bulk API `MUST` recognize `atomic`, even if it supports only one mode, and `MUST` reject | ||
| with `422` a value it does not support. Ignoring the parameter would change the outcome rather | ||
| than merely do less - see | ||
| [Unrecognized fields, query parameters, and headers](../unrecognized-fields-query-parameters-and-headers.md). | ||
| - Problems with the request itself - a malformed `filter`, too many items, both `ids` and `filter` - | ||
| are not per-resource failures. They reject the whole request in either mode, before any resource | ||
| is processed. | ||
|
|
||
| **Example** | ||
|
|
||
| ```http | ||
| POST /api/v1/users:bulk-delete?atomic=false | ||
| ``` |
64 changes: 64 additions & 0 deletions
64
docs/architecture/server/apis/bulk-operations/bulk-actions.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| # Bulk actions | ||
|
|
||
| Notation follows the [APIs index](../index.md#notation). | ||
|
|
||
| Bulk actions are the equivalent of invoking an [action](../acting-upon-resources.md) upon each | ||
| targeted resource. | ||
|
|
||
| - API path `MUST` follow the pattern `/api/v1/{resource plural}:bulk-{action}`, where `{action}` is | ||
| the name of the single-resource action - e.g. `/api/v1/users:bulk-send-email` for | ||
| `/api/v1/users/{id}/actions/send-email`. | ||
| - Because bulk actions share a namespace with the other bulk operations, actions `MUST NOT` be named | ||
| `create`, `update`, `replace`, or `delete`. | ||
| - `data` holds exactly what the single-resource action would accept as its body, and is applied to | ||
| every targeted resource. It `MAY` be omitted if the action takes no body. | ||
|
|
||
| **Example** | ||
|
|
||
| ```http | ||
| POST /api/v1/users:bulk-send-email | ||
| Accept: application/json | ||
| Content-Type: application/json | ||
| ``` | ||
|
|
||
| ```json | ||
| { | ||
| "ids": ["62bed180-1f78-45d4-8a56-c996936a2947", "cacba8c1-29fa-4018-8950-acd400ec76b7"], | ||
| "data": { | ||
| "subject": "Hello", | ||
| "body": "World!" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## Input that differs per resource | ||
|
|
||
| Actions that require per-resource bodies specify the data as an array. | ||
|
|
||
| - It `MUST` be used with `ids`, not `filter`, and `MUST` be the same length. `data[n]` is the body | ||
|
mzieniukbw marked this conversation as resolved.
Resolved
|
||
| for `ids[n]`. | ||
| - A failure is reported against `/data/{n}`. | ||
|
|
||
| **Example** | ||
|
|
||
| ```http | ||
| POST /api/v1/users:bulk-send-email | ||
| Accept: application/json | ||
| Content-Type: application/json | ||
| ``` | ||
|
|
||
| ```json | ||
| { | ||
| "ids": ["62bed180-1f78-45d4-8a56-c996936a2947", "cacba8c1-29fa-4018-8950-acd400ec76b7"], | ||
| "data": [ | ||
| { | ||
| "subject": "Hello.", | ||
| "body": "Hi, John!" | ||
| }, | ||
| { | ||
| "subject": "Hello", | ||
| "body": "Hi, Mary!" | ||
| } | ||
| ] | ||
| } | ||
| ``` | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π¨ The category is very long. I'd consider whether introducing a second grouping, consistent with the TOC, would help with organizing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I don't know what you mean by "The category is very long."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean the child pages?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the page list within the "apis" category has a ton of entries in it. It would be helpful if the grouping in the sidebar reflected the grouping in the index.