AIP-136

Custom operations

Services use custom operations to provide a means to express arbitrary actions that are difficult to model using only the standard operations. Custom operations are important because they provide a means for an API's vocabulary to adhere to user intent.

Guidance

Custom operations should only be used for functionality that can not be easily expressed via standard operations; prefer standard operations if possible, due to their consistent semantics. (Of course, this only applies if the functionality in question actually conforms to the normal semantics; it is not a good idea to contort things to endeavor to make the standard operations "sort of work".)

While custom operations vary widely in how they are designed, many principles apply consistently:

// Archives the given book.
rpc ArchiveBook(ArchiveBookRequest) returns (ArchiveBookResponse) {
  option (google.api.http) = {
    post: "/v1/{name=publishers/*/books/*}:archive"
    body: "*"
  };
}
  • The name of the RPC should be a verb followed by a noun.
    • The name must not contain prepositions ("for", "with", etc.).
  • The HTTP method for custom operations should usually be POST, unless the custom method maps more strongly to another HTTP verb.
    • Custom operations that serve as an alternative to get or list operations (such as Search) should use GET. These operations must be idempotent and have no state changes or side effects (they should be safe as defined in RFC 7231).
    • Custom operations should not use PATCH or DELETE.
  • The HTTP URI must use a : character followed by the custom verb (:archive in the above example), and the verb in the URI must match the verb in the name of the RPC.
    • If word separation is required, camelCase must be used.
  • The body clause in the google.api.http annotation should be "*".
    • However, if using GET or DELETE, the body clause must be absent.
  • Custom operations should usually take a request message matching the RPC name, with a -Request suffix.
  • Custom operations should usually return a response message matching the RPC name, with a -Response suffix.
    • When operating on a specific resource, a custom method may return the resource itself.
/publishers/{publisherId}/books/{bookId}:archive:
  post:
    operationId: archiveBook
    description: Archives the given book.
    requestBody:
      description:
      content:
        application/json:
          schema:
            description: Request structure to archive a book.
            properties:
              mime_type:
                type: string
                description: |
                  The target format for the archived book.
                  Must be "application/pdf", "application/rtf", or
                  "application/epub+zip"
                required: true
                enum:
                  - application/pdf
                  - application/rtf
                  - application/epub+zip
    responses:
      200:
        description: OK
        content:
          application/json:
            schema:
              description: Response structure for the archiveBook operation.
              properties:
                uri:
                  type: string
                  description: The location of the archived book.
  • The operationId should be a verb followed by a noun.
    • The operationId must not contain prepositions ("for", "with", etc.).
  • The HTTP method for custom operations should usually be POST, unless the custom method maps more strongly to another HTTP verb.
    • Custom operations that serve as an alternative to get or list operations (such as Search) should use GET, and require no request body. These operations must be idempotent and have no state changes or side effects (they should be safe as defined in RFC 7231).
    • Custom operations should not use PATCH or DELETE.
  • The HTTP URI must use a : character followed by the custom verb (:archive in the above example), and the verb in the URI must match the verb in the operationId.
    • If word separation is required, camelCase must be used.

Note: The pattern above shows a custom method that operates on a specific resource. Custom operations can be associated with resources, collections, or services.

Collection-based custom operations

While most custom operations operate on a single resource, some custom operations may operate on a collection instead:

// Sorts the books from this publisher.
rpc SortBooks(SortBooksRequest) returns (SortBooksResponse) {
  option (google.api.http) = {
    post: "/v1/{publisher=publishers/*}/books:sort"
    body: "*"
  };
}
/publishers/{publisherId}/books:sort:
  post:
    operationId: sortBooks
    description: Sorts the books from this publisher.
    requestBody:
      description: Request structure to sort a collection of books.
      properties:
        field:
          type: string
          description: |
            The property of the book to sort by.
            If not provided, "title" is used.
    responses:
      200:
        description: OK
  • If the collection has a parent, the field name in the request message should be the target resource's singular noun (publisher in the above example). If word separators are necessary, snake_case must be used.
  • The collection key (books in the above example) must be literal.

Stateless operations

Some custom operations are not attached to resources at all. These operations are generally stateless: they accept a request and return a response, and have no permanent effect on data within the API.

// Translates the provided text from one language to another.
rpc TranslateText(TranslateTextRequest) returns (TranslateTextResponse) {
  option (google.api.http) = {
    post: "/v1/{project=projects/*}:translateText"
    body: "*"
  };
}
/projects/{projectId}:translateText:
  post:
    operationId: translateText
    description: Translates the provided text from one language to another.
    requestBody:
      description:
      content:
        application/json:
          schema:
            description: Request structure to translate text.
            properties:
              contents:
                type: array
                items:
                  type: string
                description: The contents of the input, as a string.
              source_language_code:
                type: string
                description: |
                  The BCP-47 language code of the input text (e.g. "en-US").
                  If the source language is not specified, the service will
                  attempt to infer it.
              target_language_code:
                type: string
                description: |
                  The BCP-47 language code of the output text (e.g. "en-US").
    responses:
      200:
        description: OK
        content:
          application/json:
            schema:
              description: |
                Response structure for the translateText operation.
              properties:
                translated_text:
                  type: string
                  description: |
                    Text translated into the target language.
                detected_language_code:
                  type: string
                  description: |
                    The BCP-47 language code of source text in the initial
                    request, if it was detected automatically.
  • If the method runs in a particular scope (such as a project, as in the above example), the field name in the request message should be the name of the scope resource. If word separators are necessary, snake_case must be used.
  • The URI should place both the verb and noun after the : separator (avoid a "faux collection key" in the URI in this case, as there is no collection). For example, :translateText is preferable to text:translate.
  • Stateless operations must use POST if they involve billing.

Declarative-friendly resources

Declarative-friendly resources usually should not employ custom operations (except specific declarative-friendly custom operations discussed in other AIPs), because declarative-friendly tools are unable to automatically determine what to do with them.

An exception to this is for rarely-used, fundamentally imperative operations, such as a Move, Rename, or Restart operation, for which there would not be an expectation of declarative support.