diff --git a/rbtools/api/resource/__init__.py b/rbtools/api/resource/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..f508535ff97676e3b41301e5ebec9d7bf534b24b
--- /dev/null
+++ b/rbtools/api/resource/__init__.py
@@ -0,0 +1,68 @@
+"""Resource definitions for the RBTools Python API."""
+
+from __future__ import annotations
+
+from .base import (
+    CountResource,
+    ItemResource,
+    ListResource,
+    RESOURCE_MAP,
+    Resource,
+    ResourceDictField,
+    ResourceExtraDataField,
+    ResourceLinkField,
+    ResourceListField,
+    resource_mimetype,
+)
+from .diff import (
+    DiffListResource,
+    DiffResource,
+)
+from .diff_commit import DiffCommitItemResource
+from .diff_file_attachment import DiffFileAttachmentListResource
+from .draft_diff import DraftDiffResource
+from .draft_diff_commit import DraftDiffCommitItemResource
+from .draft_file_attachment import DraftFileAttachmentListResource
+from .draft_screenshot import DraftScreenshotListResource
+from .file_attachment import FileAttachmentListResource
+from .file_diff import FileDiffResource
+from .mixins import (
+    DiffUploaderMixin,
+    GetPatchMixin,
+)
+from .review_request import ReviewRequestResource
+from .root import RootResource
+from .screenshot import ScreenshotListResource
+from .validate_diff import ValidateDiffResource
+from .validate_diff_commit import ValidateDiffCommitResource
+
+
+__all__ = [
+    'CountResource',
+    'DiffCommitItemResource',
+    'DiffFileAttachmentListResource',
+    'DiffListResource',
+    'DiffResource',
+    'DiffUploaderMixin',
+    'DraftDiffCommitItemResource',
+    'DraftDiffResource',
+    'DraftFileAttachmentListResource',
+    'DraftScreenshotListResource',
+    'FileAttachmentListResource',
+    'FileDiffResource',
+    'GetPatchMixin',
+    'ItemResource',
+    'ListResource',
+    'RESOURCE_MAP',
+    'Resource',
+    'ResourceDictField',
+    'ResourceExtraDataField',
+    'ResourceLinkField',
+    'ResourceListField',
+    'ReviewRequestResource',
+    'RootResource',
+    'ScreenshotListResource',
+    'ValidateDiffCommitResource',
+    'ValidateDiffResource',
+    'resource_mimetype',
+]
diff --git a/rbtools/api/resource.py b/rbtools/api/resource/base.py
similarity index 60%
rename from rbtools/api/resource.py
rename to rbtools/api/resource/base.py
index 47ab380f69a84d15702edd7de9f36c3d881f9d66..59391a20d507fe04a95d7d1c4e970fc27875129c 100644
--- a/rbtools/api/resource.py
+++ b/rbtools/api/resource/base.py
@@ -1,25 +1,21 @@
-"""Resource definitions for the RBTools Python API."""
+"""Resource definitions for the RBTools Python API.
+
+Version Added:
+    6.0:
+    This was moved from :py:module:`rbtools.api.resource`.
+"""
 
 from __future__ import annotations
 
-from typing import Any, Iterator, Optional
-
 import copy
 import json
-import logging
-import re
-from collections import defaultdict, deque
-from collections.abc import MutableMapping
-from typing import Optional
+from collections.abc import Iterator, MutableMapping
+from typing import Any, Optional
 from urllib.parse import urljoin
 
-from packaging.version import parse as parse_version
-
-from rbtools.api.cache import MINIMUM_VERSION
 from rbtools.api.decorators import request_method_decorator
 from rbtools.api.request import HttpRequest
 from rbtools.api.utils import rem_mime_format
-from rbtools.utils.graphs import path_exists
 
 
 RESOURCE_MAP = {}
@@ -1026,670 +1022,3 @@ class ListResource(Resource):
                                         self._url,
                                         self._token,
                                         self._item_mime_type))
-
-
-class GetPatchMixin:
-    """Mixin for resources that implement a get_patch method.
-
-    Version Added:
-        4.2
-    """
-
-    @request_method_decorator
-    def get_patch(self, **kwargs):
-        """Retrieve the diff file contents.
-
-        Args:
-            **kwargs (dict):
-                Query args to pass to
-                :py:meth:`~rbtools.api.request.HttpRequest.__init__`.
-
-        Returns:
-            ItemResource:
-            A resource payload whose :py:attr:`~ItemResource.data` attribute is
-            the requested patch.
-        """
-        return HttpRequest(self._url, query_args=kwargs, headers={
-            'Accept': 'text/x-patch',
-        })
-
-
-@resource_mimetype('application/vnd.reviewboard.org.root')
-class RootResource(ItemResource):
-    """The Root resource specific base class.
-
-    Provides additional methods for fetching any resource directly
-    using the uri templates. A method of the form "get_<uri-template-name>"
-    is called to retrieve the HttpRequest corresponding to the
-    resource. Template replacement values should be passed in as a
-    dictionary to the values parameter.
-    """
-
-    #: Capabilities for the Review Board server.
-    capabilities: ResourceDictField
-
-    _excluded_attrs = ['uri_templates']
-    _TEMPLATE_PARAM_RE = re.compile(r'\{(?P<key>[A-Za-z_0-9]*)\}')
-
-    def __init__(self, transport, payload, url, **kwargs):
-        super(RootResource, self).__init__(transport, payload, url, token=None)
-        # Generate methods for accessing resources directly using
-        # the uri-templates.
-        for name, url in payload['uri_templates'].items():
-            attr_name = 'get_%s' % name
-
-            if not hasattr(self, attr_name):
-                setattr(self,
-                        attr_name,
-                        lambda resource=self, url=url, **kwargs: (
-                            self._get_template_request(url, **kwargs)))
-
-        server_version = payload.get('product', {}).get('package_version')
-
-        if (server_version is None or
-            parse_version(server_version) < parse_version(MINIMUM_VERSION)):
-            # This version is too old to safely support caching (there were
-            # bugs before this version). Disable caching.
-            transport.disable_cache()
-
-    @request_method_decorator
-    def _get_template_request(self, url_template, values={}, **kwargs):
-        """Generate an HttpRequest from a uri-template.
-
-        This will replace each '{variable}' in the template with the
-        value from kwargs['variable'], or if it does not exist, the
-        value from values['variable']. The resulting url is used to
-        create an HttpRequest.
-        """
-        def get_template_value(m):
-            try:
-                return str(kwargs.pop(m.group('key'), None) or
-                           values[m.group('key')])
-            except KeyError:
-                raise ValueError('Template was not provided a value for "%s"' %
-                                 m.group('key'))
-
-        url = self._TEMPLATE_PARAM_RE.sub(get_template_value, url_template)
-        return HttpRequest(url, query_args=kwargs)
-
-
-@resource_mimetype('application/vnd.reviewboard.org.commit')
-class DiffCommitItemResource(GetPatchMixin, ItemResource):
-    """The commit resource-specific class."""
-
-
-@resource_mimetype('application/vnd.reviewboard.org.draft-commit')
-class DraftDiffCommitItemResource(GetPatchMixin, ItemResource):
-    """The draft commit resource-specific class.
-
-    Version Added:
-        4.2
-    """
-
-
-@resource_mimetype('application/vnd.reviewboard.org.draft-commits')
-class DraftDiffCommitListResource(ListResource):
-    """The draft commit list resource-specific class.
-
-    Provides additional functionality in the uploading of new commits.
-    """
-
-    @request_method_decorator
-    def upload_commit(self, validation_info, diff, commit_id, parent_id,
-                      author_name, author_email, author_date, commit_message,
-                      committer_name=None, committer_email=None,
-                      committer_date=None, parent_diff=None, **kwargs):
-        """Upload a commit.
-
-        Args:
-            validation_info (unicode):
-                The validation info, or ``None`` if this is the first commit in
-                a series.
-
-            diff (bytes):
-                The diff contents.
-
-            commit_id (unicode):
-                The ID of the commit being uploaded.
-
-            parent_id (unicode):
-                The ID of the parent commit.
-
-            author_name (unicode):
-                The name of the author.
-
-            author_email (unicode):
-                The e-mail address of the author.
-
-            author_date (unicode):
-                The date and time the commit was authored in ISO 8601 format.
-
-            committer_name (unicode, optional):
-                The name of the committer (if applicable).
-
-            committer_email (unicode, optional):
-                The e-mail address of the committer (if applicable).
-
-            committer_date (unicode, optional):
-                The date and time the commit was committed in ISO 8601 format
-                (if applicable).
-
-            parent_diff (bytes, optional):
-                The contents of the parent diff.
-
-            **kwargs (dict):
-                Keyword argument used to build the querystring for the request
-                URL.
-
-        Returns:
-            DraftDiffCommitItemResource:
-            The created resource.
-
-        Raises:
-            rbtools.api.errors.APIError:
-                An error occurred while uploading the commit.
-        """
-        request = HttpRequest(self._url, method='POST', query_args=kwargs)
-
-        request.add_file('diff', 'diff', diff)
-        request.add_field('commit_id', commit_id)
-        request.add_field('parent_id', parent_id)
-        request.add_field('commit_message', commit_message)
-        request.add_field('author_name', author_name)
-        request.add_field('author_email', author_email)
-        request.add_field('author_date', author_date)
-
-        if validation_info:
-            request.add_field('validation_info', validation_info)
-
-        if committer_name and committer_email and committer_date:
-            request.add_field('committer_name', committer_name)
-            request.add_field('committer_email', committer_email)
-            request.add_field('committer_date', committer_date)
-        elif committer_name or committer_email or committer_name:
-            logging.warning(
-                'Either all or none of committer_name, committer_email, and '
-                'committer_date must be provided to upload_commit. None of '
-                'these fields will be submitted.'
-            )
-
-        if parent_diff:
-            request.add_file('parent_diff', 'parent_diff', parent_diff)
-
-        return request
-
-
-class DiffUploaderMixin(object):
-    """A mixin for uploading diffs to a resource."""
-
-    def prepare_upload_diff_request(self, diff, parent_diff=None,
-                                    base_dir=None, base_commit_id=None,
-                                    **kwargs):
-        """Create a request that can be used to upload a diff.
-
-        The diff and parent_diff arguments should be strings containing the
-        diff output.
-        """
-        request = HttpRequest(self._url, method='POST', query_args=kwargs)
-        request.add_file('path', 'diff', diff)
-
-        if parent_diff:
-            request.add_file('parent_diff_path', 'parent_diff', parent_diff)
-
-        if base_dir:
-            request.add_field('basedir', base_dir)
-
-        if base_commit_id:
-            request.add_field('base_commit_id', base_commit_id)
-
-        return request
-
-
-@resource_mimetype('application/vnd.reviewboard.org.diffs')
-class DiffListResource(DiffUploaderMixin, ListResource):
-    """The Diff List resource specific base class.
-
-    This resource provides functionality to assist in the uploading of new
-    diffs.
-    """
-
-    @request_method_decorator
-    def upload_diff(self, diff, parent_diff=None, base_dir=None,
-                    base_commit_id=None, **kwargs):
-        """Upload a diff to the resource.
-
-        The diff and parent_diff arguments should be strings containing the
-        diff output.
-        """
-        return self.prepare_upload_diff_request(
-            diff,
-            parent_diff=parent_diff,
-            base_dir=base_dir,
-            base_commit_id=base_commit_id,
-            **kwargs)
-
-    @request_method_decorator
-    def create_empty(self, base_commit_id=None, **kwargs):
-        """Create an empty DiffSet that commits can be added to.
-
-        Args:
-            base_commit_id (unicode, optional):
-                The base commit ID of the diff.
-
-            **kwargs (dict):
-                Keyword arguments to encode into the querystring of the request
-                URL.
-        Returns:
-            DiffItemResource:
-            The created resource.
-        """
-        request = HttpRequest(self._url, method='POST', query_args=kwargs)
-
-        if base_commit_id:
-            request.add_field('base_commit_id', base_commit_id)
-
-        return request
-
-
-@resource_mimetype('application/vnd.reviewboard.org.diff')
-class DiffResource(GetPatchMixin, ItemResource):
-    """The Diff resource specific base class.
-
-    Provides the 'get_patch' method for retrieving the content of the
-    actual diff file itself.
-    """
-
-    @request_method_decorator
-    def finalize_commit_series(self, cumulative_diff, validation_info,
-                               parent_diff=None):
-        """Finalize a commit series.
-
-        Args:
-            cumulative_diff (bytes):
-                The cumulative diff of the entire commit series.
-
-            validation_info (unicode):
-                The validation information returned by validatin the last
-                commit in the series with the
-                :py:class:`ValidateDiffCommitResource`.
-
-            parent_diff (bytes, optional):
-                An optional parent diff.
-
-                This will be the same parent diff uploaded with each commit.
-
-        Returns:
-            DiffItemResource:
-            The finalized diff resource.
-        """
-        if not isinstance(cumulative_diff, bytes):
-            raise TypeError('cumulative_diff must be byte string, not %s'
-                            % type(cumulative_diff))
-
-        if parent_diff is not None and not isinstance(parent_diff, bytes):
-            raise TypeError('parent_diff must be byte string, not %s'
-                            % type(cumulative_diff))
-
-        request = HttpRequest(self.links['self']['href'],
-                              method='PUT')
-
-        request.add_field('finalize_commit_series', True)
-        request.add_file('cumulative_diff', 'cumulative_diff',
-                         cumulative_diff)
-        request.add_field('validation_info', validation_info)
-
-        if parent_diff is not None:
-            request.add_file('parent_diff', 'parent_diff', parent_diff)
-
-        return request
-
-
-@resource_mimetype('application/vnd.reviewboard.org.draft-diff')
-class DraftDiffResource(GetPatchMixin, ItemResource):
-    """The Draft Diff resource specific base class.
-
-    Provides the :py:meth:`get_patch` method for retrieving the content of the
-    actual diff file itself.
-
-    Version Added:
-        4.2
-    """
-
-
-@resource_mimetype('application/vnd.reviewboard.org.file')
-class FileDiffResource(GetPatchMixin, ItemResource):
-    """The File Diff resource specific base class."""
-
-    @request_method_decorator
-    def get_diff_data(self, **kwargs):
-        """Retrieves the actual raw diff data for the file."""
-        return HttpRequest(self._url, query_args=kwargs, headers={
-            'Accept': 'application/vnd.reviewboard.org.diff.data+json',
-        })
-
-
-@resource_mimetype('application/vnd.reviewboard.org.file-attachments')
-@resource_mimetype('application/vnd.reviewboard.org.user-file-attachments')
-class FileAttachmentListResource(ListResource):
-    """The File Attachment List resource specific base class."""
-
-    @request_method_decorator
-    def upload_attachment(
-        self,
-        filename: str,
-        content: bytes,
-        caption: Optional[str] = None,
-        attachment_history: Optional[str] = None,
-        **kwargs,
-    ) -> HttpRequest:
-        """Upload a new attachment.
-
-        Args:
-            filename (str):
-                The name of the file.
-
-            content (bytes):
-                The content of the file to upload.
-
-            caption (str, optional):
-                The caption to set on the file attachment.
-
-            attachment_history (str, optional):
-                The ID of the FileAttachmentHistory to add this attachment to.
-
-            **kwargs (dict):
-                Additional keyword arguments to add to the request.
-        """
-        request = HttpRequest(self._url, method='POST', query_args=kwargs)
-        request.add_file('path', filename, content)
-
-        if caption:
-            request.add_field('caption', caption)
-
-        if attachment_history:
-            request.add_field('attachment_history', attachment_history)
-
-        return request
-
-
-@resource_mimetype('application/vnd.reviewboard.org.diff-file-attachments')
-class DiffFileAttachmentListResource(ListResource):
-    """The Diff File Attachment List resource specific base class.
-
-    Version Added:
-        5.0
-    """
-
-    @request_method_decorator
-    def upload_attachment(
-        self,
-        *,
-        filename: str,
-        content: bytes,
-        filediff_id: str,
-        source_file: bool = False,
-        **kwargs,
-    ) -> HttpRequest:
-        """Upload a new attachment.
-
-        Args:
-            filename (str):
-                The name of the file.
-
-            content (bytes):
-                The content of the file to upload.
-
-            filediff_id (str):
-                The ID of the filediff to attach the file to.
-
-            source_file (bool, optional):
-                Whether to upload the source version of a file.
-
-            **kwargs (dict):
-                Additional keyword arguments to add to the request
-
-        Returns:
-            rbtools.api.request.HttpRequest:
-            The request object.
-        """
-        request = self.create(query_args=kwargs, internal=True)
-        request.add_file('path', filename, content)
-        request.add_field('filediff', filediff_id)
-
-        if source_file:
-            request.add_field('source_file', '1')
-
-        return request
-
-
-@resource_mimetype('application/vnd.reviewboard.org.draft-file-attachments')
-class DraftFileAttachmentListResource(FileAttachmentListResource):
-    """The Draft File Attachment List resource specific base class."""
-    pass
-
-
-@resource_mimetype('application/vnd.reviewboard.org.screenshots')
-class ScreenshotListResource(ListResource):
-    """The Screenshot List resource specific base class."""
-    @request_method_decorator
-    def upload_screenshot(self, filename, content, caption=None, **kwargs):
-        """Uploads a new screenshot.
-
-        The content argument should contain the body of the screenshot
-        to be uploaded, in string format.
-        """
-        request = HttpRequest(self._url, method='POST', query_args=kwargs)
-        request.add_file('path', filename, content)
-
-        if caption:
-            request.add_field('caption', caption)
-
-        return request
-
-
-@resource_mimetype('application/vnd.reviewboard.org.draft-screenshots')
-class DraftScreenshotListResource(ScreenshotListResource):
-    """The Draft Screenshot List resource specific base class."""
-    pass
-
-
-@resource_mimetype('application/vnd.reviewboard.org.review-request')
-class ReviewRequestResource(ItemResource):
-    """The Review Request resource specific base class."""
-
-    @property
-    def absolute_url(self):
-        """Returns the absolute URL for the Review Request.
-
-        The value of absolute_url is returned if it's defined.
-        Otherwise the absolute URL is generated and returned.
-        """
-        if 'absolute_url' in self._fields:
-            return self._fields['absolute_url']
-        else:
-            base_url = self._url.split('/api/')[0]
-            return urljoin(base_url, self.url)
-
-    @property
-    def url(self):
-        """Returns the relative URL to the Review Request.
-
-        The value of 'url' is returned if it's defined. Otherwise, a relative
-        URL is generated and returned.
-
-        This provides compatibility with versions of Review Board older
-        than 1.7.8, which do not have a 'url' field.
-        """
-        return self._fields.get('url', '/r/%s/' % self.id)
-
-    @request_method_decorator
-    def submit(self, description=None, changenum=None):
-        """Submit a review request"""
-        data = {
-            'status': 'submitted',
-        }
-
-        if description:
-            data['description'] = description
-
-        if changenum:
-            data['changenum'] = changenum
-
-        return self.update(data=data, internal=True)
-
-    @request_method_decorator
-    def get_or_create_draft(self, **kwargs):
-        request = self.get_draft(internal=True)
-        request.method = 'POST'
-
-        for name, value in kwargs.items():
-            request.add_field(name, value)
-
-        return request
-
-    def build_dependency_graph(self):
-        """Build the dependency graph for the review request.
-
-        Only review requests in the same repository as this one will be in the
-        graph.
-
-        A ValueError is raised if the graph would contain cycles.
-        """
-        def get_url(resource):
-            """Get the URL of the resource."""
-            if hasattr(resource, 'href'):
-                return resource.href
-            else:
-                return resource.absolute_url
-
-        # Even with the API cache, we don't want to be making more requests
-        # than necessary. The review request resource will be cached by an
-        # ETag, so there will still be a round trip if we don't cache them
-        # here.
-        review_requests_by_url = {}
-        review_requests_by_url[self.absolute_url] = self
-
-        def get_review_request_resource(resource):
-            url = get_url(resource)
-
-            if url not in review_requests_by_url:
-                review_requests_by_url[url] = resource.get(expand='repository')
-
-            return review_requests_by_url[url]
-
-        repository = self.get_repository()
-
-        graph = defaultdict(set)
-
-        visited = set()
-
-        unvisited = deque()
-        unvisited.append(self)
-
-        while unvisited:
-            head = unvisited.popleft()
-
-            if head in visited:
-                continue
-
-            visited.add(get_url(head))
-
-            for tail in head.depends_on:
-                tail = get_review_request_resource(tail)
-
-                if path_exists(graph, tail.id, head.id):
-                    raise ValueError('Circular dependencies.')
-
-                # We don't want to include review requests for other
-                # repositories, so we'll stop if we reach one. We also don't
-                # want to re-land submitted review requests.
-                if (repository.id == tail.repository.id and
-                    tail.status != 'submitted'):
-                    graph[head].add(tail)
-                    unvisited.append(tail)
-
-        graph.default_factory = None
-        return graph
-
-
-@resource_mimetype('application/vnd.reviewboard.org.diff-validation')
-class ValidateDiffResource(DiffUploaderMixin, ItemResource):
-    """The Validate Diff resource specific base class.
-
-    Provides additional functionality to assist in the validation of diffs.
-    """
-
-    @request_method_decorator
-    def validate_diff(self, repository, diff, parent_diff=None, base_dir=None,
-                      base_commit_id=None, **kwargs):
-        """Validate a diff."""
-        request = self.prepare_upload_diff_request(
-            diff,
-            parent_diff=parent_diff,
-            base_dir=base_dir,
-            base_commit_id=base_commit_id,
-            **kwargs)
-
-        request.add_field('repository', repository)
-
-        if base_commit_id:
-            request.add_field('base_commit_id', base_commit_id)
-
-        return request
-
-
-@resource_mimetype('application/vnd.reviewboard.org.commit-validation')
-class ValidateDiffCommitResource(ItemResource):
-    """The commit validation resource specific base class."""
-
-    @request_method_decorator
-    def validate_commit(self, repository, diff, commit_id, parent_id,
-                        parent_diff=None, base_commit_id=None,
-                        validation_info=None, **kwargs):
-        """Validate the diff for a commit.
-
-        Args:
-            repository (unicode):
-                The name of the repository.
-
-            diff (bytes):
-                The contents of the diff to validate.
-
-            commit_id (unicode):
-                The ID of the commit being validated.
-
-            parent_id (unicode):
-                The ID of the parent commit.
-
-            parent_diff (bytes, optional):
-                The contents of the parent diff.
-
-            base_commit_id (unicode, optional):
-                The base commit ID.
-
-            validation_info (unicode, optional):
-                Validation information from a previous call to this resource.
-
-            **kwargs (dict):
-                Keyword arguments used to build the querystring.
-
-        Returns:
-            ValidateDiffCommitResource:
-            The validation result.
-        """
-        request = HttpRequest(self._url, method='POST', query_args=kwargs)
-        request.add_file('diff', 'diff', diff)
-        request.add_field('repository', repository)
-        request.add_field('commit_id', commit_id)
-        request.add_field('parent_id', parent_id)
-
-        if parent_diff:
-            request.add_file('parent_diff', 'parent_diff', parent_diff)
-
-        if base_commit_id:
-            request.add_field('base_commit_id', base_commit_id)
-
-        if validation_info:
-            request.add_field('validation_info', validation_info)
-
-        return request
diff --git a/rbtools/api/resource/diff.py b/rbtools/api/resource/diff.py
new file mode 100644
index 0000000000000000000000000000000000000000..0e569e3983875b01971669abc93f371ddbbad46d
--- /dev/null
+++ b/rbtools/api/resource/diff.py
@@ -0,0 +1,116 @@
+"""Resource definitions for diffs.
+
+Version Added:
+    6.0:
+    This was moved from :py:module:`rbtools.api.resource`.
+"""
+
+from __future__ import annotations
+
+from rbtools.api.decorators import request_method_decorator
+from rbtools.api.request import HttpRequest
+from rbtools.api.resource.base import (
+    ItemResource,
+    ListResource,
+    resource_mimetype,
+)
+from rbtools.api.resource.mixins import DiffUploaderMixin, GetPatchMixin
+
+
+@resource_mimetype('application/vnd.reviewboard.org.diff')
+class DiffResource(GetPatchMixin, ItemResource):
+    """The Diff resource specific base class.
+
+    Provides the 'get_patch' method for retrieving the content of the
+    actual diff file itself.
+    """
+
+    @request_method_decorator
+    def finalize_commit_series(self, cumulative_diff, validation_info,
+                               parent_diff=None):
+        """Finalize a commit series.
+
+        Args:
+            cumulative_diff (bytes):
+                The cumulative diff of the entire commit series.
+
+            validation_info (unicode):
+                The validation information returned by validatin the last
+                commit in the series with the
+                :py:class:`ValidateDiffCommitResource`.
+
+            parent_diff (bytes, optional):
+                An optional parent diff.
+
+                This will be the same parent diff uploaded with each commit.
+
+        Returns:
+            DiffItemResource:
+            The finalized diff resource.
+        """
+        if not isinstance(cumulative_diff, bytes):
+            raise TypeError('cumulative_diff must be byte string, not %s'
+                            % type(cumulative_diff))
+
+        if parent_diff is not None and not isinstance(parent_diff, bytes):
+            raise TypeError('parent_diff must be byte string, not %s'
+                            % type(cumulative_diff))
+
+        request = HttpRequest(self.links['self']['href'],
+                              method='PUT')
+
+        request.add_field('finalize_commit_series', True)
+        request.add_file('cumulative_diff', 'cumulative_diff',
+                         cumulative_diff)
+        request.add_field('validation_info', validation_info)
+
+        if parent_diff is not None:
+            request.add_file('parent_diff', 'parent_diff', parent_diff)
+
+        return request
+
+
+@resource_mimetype('application/vnd.reviewboard.org.diffs')
+class DiffListResource(DiffUploaderMixin, ListResource):
+    """The Diff List resource specific base class.
+
+    This resource provides functionality to assist in the uploading of new
+    diffs.
+    """
+
+    @request_method_decorator
+    def upload_diff(self, diff, parent_diff=None, base_dir=None,
+                    base_commit_id=None, **kwargs):
+        """Upload a diff to the resource.
+
+        The diff and parent_diff arguments should be strings containing the
+        diff output.
+        """
+        return self.prepare_upload_diff_request(
+            diff,
+            parent_diff=parent_diff,
+            base_dir=base_dir,
+            base_commit_id=base_commit_id,
+            **kwargs)
+
+    @request_method_decorator
+    def create_empty(self, base_commit_id=None, **kwargs):
+        """Create an empty DiffSet that commits can be added to.
+
+        Args:
+            base_commit_id (unicode, optional):
+                The base commit ID of the diff.
+
+            **kwargs (dict):
+                Keyword arguments to encode into the querystring of the request
+                URL.
+        Returns:
+            DiffItemResource:
+            The created resource.
+        """
+        request = HttpRequest(self._url, method='POST', query_args=kwargs)
+
+        if base_commit_id:
+            request.add_field('base_commit_id', base_commit_id)
+
+        return request
diff --git a/rbtools/api/resource/diff_commit.py b/rbtools/api/resource/diff_commit.py
new file mode 100644
index 0000000000000000000000000000000000000000..778ce93fbf67340b2228e61d74b03da97fd9df11
--- /dev/null
+++ b/rbtools/api/resource/diff_commit.py
@@ -0,0 +1,19 @@
+"""Resource definitions for diff commits.
+
+Version Added:
+    6.0:
+    This was moved from :py:module:`rbtools.api.resource`.
+"""
+
+from __future__ import annotations
+
+from rbtools.api.resource.base import (
+    ItemResource,
+    resource_mimetype,
+)
+from rbtools.api.resource.mixins import GetPatchMixin
+
+
+@resource_mimetype('application/vnd.reviewboard.org.commit')
+class DiffCommitItemResource(GetPatchMixin, ItemResource):
+    """The commit resource-specific class."""
diff --git a/rbtools/api/resource/diff_file_attachment.py b/rbtools/api/resource/diff_file_attachment.py
new file mode 100644
index 0000000000000000000000000000000000000000..2407318aae9ab8a288ed12d7c18b97f3aec0f350
--- /dev/null
+++ b/rbtools/api/resource/diff_file_attachment.py
@@ -0,0 +1,66 @@
+"""Resource definitions for diff file attachments.
+
+Version Added:
+    6.0:
+    This was moved from :py:module:`rbtools.api.resource`.
+"""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from rbtools.api.decorators import request_method_decorator
+from rbtools.api.resource.base import ListResource, resource_mimetype
+
+if TYPE_CHECKING:
+    from rbtools.api.request import HttpRequest
+
+
+@resource_mimetype('application/vnd.reviewboard.org.diff-file-attachments')
+class DiffFileAttachmentListResource(ListResource):
+    """The Diff File Attachment List resource specific base class.
+
+    Version Added:
+        5.0
+    """
+
+    @request_method_decorator
+    def upload_attachment(
+        self,
+        *,
+        filename: str,
+        content: bytes,
+        filediff_id: str,
+        source_file: bool = False,
+        **kwargs,
+    ) -> HttpRequest:
+        """Upload a new attachment.
+
+        Args:
+            filename (str):
+                The name of the file.
+
+            content (bytes):
+                The content of the file to upload.
+
+            filediff_id (str):
+                The ID of the filediff to attach the file to.
+
+            source_file (bool, optional):
+                Whether to upload the source version of a file.
+
+            **kwargs (dict):
+                Additional keyword arguments to add to the request
+
+        Returns:
+            rbtools.api.request.HttpRequest:
+            The request object.
+        """
+        request = self.create(query_args=kwargs, internal=True)
+        request.add_file('path', filename, content)
+        request.add_field('filediff', filediff_id)
+
+        if source_file:
+            request.add_field('source_file', '1')
+
+        return request
diff --git a/rbtools/api/resource/draft_diff.py b/rbtools/api/resource/draft_diff.py
new file mode 100644
index 0000000000000000000000000000000000000000..56b257ec0a6fba1fb067e9a4ca94860f62a686dd
--- /dev/null
+++ b/rbtools/api/resource/draft_diff.py
@@ -0,0 +1,26 @@
+"""Resource definitions for draft diffs.
+
+Version Added:
+    6.0:
+    This was moved from :py:module:`rbtools.api.resource`.
+"""
+
+from __future__ import annotations
+
+from rbtools.api.resource.base import (
+    ItemResource,
+    resource_mimetype,
+)
+from rbtools.api.resource.mixins import GetPatchMixin
+
+
+@resource_mimetype('application/vnd.reviewboard.org.draft-diff')
+class DraftDiffResource(GetPatchMixin, ItemResource):
+    """The Draft Diff resource specific base class.
+
+    Provides the :py:meth:`get_patch` method for retrieving the content of the
+    actual diff file itself.
+
+    Version Added:
+        4.2
+    """
diff --git a/rbtools/api/resource/draft_diff_commit.py b/rbtools/api/resource/draft_diff_commit.py
new file mode 100644
index 0000000000000000000000000000000000000000..2116bbd298bbe815da663b47815cacc50f340afb
--- /dev/null
+++ b/rbtools/api/resource/draft_diff_commit.py
@@ -0,0 +1,123 @@
+"""Resource definitions for draft diff commits.
+
+Version Added:
+    6.0:
+    This was moved from :py:module:`rbtools.api.resource`.
+"""
+
+from __future__ import annotations
+
+import logging
+
+from rbtools.api.decorators import request_method_decorator
+from rbtools.api.request import HttpRequest
+from rbtools.api.resource.base import (
+    ItemResource,
+    ListResource,
+    resource_mimetype,
+)
+from rbtools.api.resource.mixins import GetPatchMixin
+
+
+logger = logging.getLogger(__name__)
+
+
+@resource_mimetype('application/vnd.reviewboard.org.draft-commit')
+class DraftDiffCommitItemResource(GetPatchMixin, ItemResource):
+    """The draft commit resource-specific class.
+
+    Version Added:
+        4.2
+    """
+
+
+@resource_mimetype('application/vnd.reviewboard.org.draft-commits')
+class DraftDiffCommitListResource(ListResource):
+    """The draft commit list resource-specific class.
+
+    Provides additional functionality in the uploading of new commits.
+    """
+
+    @request_method_decorator
+    def upload_commit(self, validation_info, diff, commit_id, parent_id,
+                      author_name, author_email, author_date, commit_message,
+                      committer_name=None, committer_email=None,
+                      committer_date=None, parent_diff=None, **kwargs):
+        """Upload a commit.
+
+        Args:
+            validation_info (unicode):
+                The validation info, or ``None`` if this is the first commit in
+                a series.
+
+            diff (bytes):
+                The diff contents.
+
+            commit_id (unicode):
+                The ID of the commit being uploaded.
+
+            parent_id (unicode):
+                The ID of the parent commit.
+
+            author_name (unicode):
+                The name of the author.
+
+            author_email (unicode):
+                The e-mail address of the author.
+
+            author_date (unicode):
+                The date and time the commit was authored in ISO 8601 format.
+
+            committer_name (unicode, optional):
+                The name of the committer (if applicable).
+
+            committer_email (unicode, optional):
+                The e-mail address of the committer (if applicable).
+
+            committer_date (unicode, optional):
+                The date and time the commit was committed in ISO 8601 format
+                (if applicable).
+
+            parent_diff (bytes, optional):
+                The contents of the parent diff.
+
+            **kwargs (dict):
+                Keyword argument used to build the querystring for the request
+                URL.
+
+        Returns:
+            DraftDiffCommitItemResource:
+            The created resource.
+
+        Raises:
+            rbtools.api.errors.APIError:
+                An error occurred while uploading the commit.
+        """
+        request = HttpRequest(self._url, method='POST', query_args=kwargs)
+
+        request.add_file('diff', 'diff', diff)
+        request.add_field('commit_id', commit_id)
+        request.add_field('parent_id', parent_id)
+        request.add_field('commit_message', commit_message)
+        request.add_field('author_name', author_name)
+        request.add_field('author_email', author_email)
+        request.add_field('author_date', author_date)
+
+        if validation_info:
+            request.add_field('validation_info', validation_info)
+
+        if committer_name and committer_email and committer_date:
+            request.add_field('committer_name', committer_name)
+            request.add_field('committer_email', committer_email)
+            request.add_field('committer_date', committer_date)
+        elif committer_name or committer_email or committer_name:
+            logger.warning(
+                'Either all or none of committer_name, committer_email, and '
+                'committer_date must be provided to upload_commit. None of '
+                'these fields will be submitted.'
+            )
+
+        if parent_diff:
+            request.add_file('parent_diff', 'parent_diff', parent_diff)
+
+        return request
diff --git a/rbtools/api/resource/draft_file_attachment.py b/rbtools/api/resource/draft_file_attachment.py
new file mode 100644
index 0000000000000000000000000000000000000000..8c8ee1b3bc7c1c5dfacf3cd520cdcdf050cbc6bf
--- /dev/null
+++ b/rbtools/api/resource/draft_file_attachment.py
@@ -0,0 +1,16 @@
+"""Resource definitions for draft file attachments.
+
+Version Added:
+    6.0:
+    This was moved from :py:module:`rbtools.api.resource`.
+"""
+
+from __future__ import annotations
+
+from rbtools.api.resource.base import resource_mimetype
+from rbtools.api.resource.file_attachment import FileAttachmentListResource
+
+
+@resource_mimetype('application/vnd.reviewboard.org.draft-file-attachments')
+class DraftFileAttachmentListResource(FileAttachmentListResource):
+    """The Draft File Attachment List resource specific base class."""
diff --git a/rbtools/api/resource/draft_screenshot.py b/rbtools/api/resource/draft_screenshot.py
new file mode 100644
index 0000000000000000000000000000000000000000..93428701191a3746f601f2556c8ef8901c9432e0
--- /dev/null
+++ b/rbtools/api/resource/draft_screenshot.py
@@ -0,0 +1,16 @@
+"""Resource definitions for draft screenshots.
+
+Version Added:
+    6.0:
+    This was moved from :py:module:`rbtools.api.resource`.
+"""
+
+from __future__ import annotations
+
+from rbtools.api.resource.base import resource_mimetype
+from rbtools.api.resource.screenshot import ScreenshotListResource
+
+
+@resource_mimetype('application/vnd.reviewboard.org.draft-screenshots')
+class DraftScreenshotListResource(ScreenshotListResource):
+    """The Draft Screenshot List resource specific base class."""
diff --git a/rbtools/api/resource/file_attachment.py b/rbtools/api/resource/file_attachment.py
new file mode 100644
index 0000000000000000000000000000000000000000..5e6df0d245ecb332fa0662449f271234923c7a9a
--- /dev/null
+++ b/rbtools/api/resource/file_attachment.py
@@ -0,0 +1,58 @@
+"""Resource definitions for file attachments.
+
+Version Added:
+    6.0:
+    This was moved from :py:module:`rbtools.api.resource`.
+"""
+
+from __future__ import annotations
+
+from typing import Optional
+
+from rbtools.api.decorators import request_method_decorator
+from rbtools.api.request import HttpRequest
+from rbtools.api.resource.base import ListResource, resource_mimetype
+
+
+@resource_mimetype('application/vnd.reviewboard.org.file-attachments')
+@resource_mimetype('application/vnd.reviewboard.org.user-file-attachments')
+class FileAttachmentListResource(ListResource):
+    """The File Attachment List resource specific base class."""
+
+    @request_method_decorator
+    def upload_attachment(
+        self,
+        filename: str,
+        content: bytes,
+        caption: Optional[str] = None,
+        attachment_history: Optional[str] = None,
+        **kwargs,
+    ) -> HttpRequest:
+        """Upload a new attachment.
+
+        Args:
+            filename (str):
+                The name of the file.
+
+            content (bytes):
+                The content of the file to upload.
+
+            caption (str, optional):
+                The caption to set on the file attachment.
+
+            attachment_history (str, optional):
+                The ID of the FileAttachmentHistory to add this attachment to.
+
+            **kwargs (dict):
+                Additional keyword arguments to add to the request.
+        """
+        request = HttpRequest(self._url, method='POST', query_args=kwargs)
+        request.add_file('path', filename, content)
+
+        if caption:
+            request.add_field('caption', caption)
+
+        if attachment_history:
+            request.add_field('attachment_history', attachment_history)
+
+        return request
diff --git a/rbtools/api/resource/file_diff.py b/rbtools/api/resource/file_diff.py
new file mode 100644
index 0000000000000000000000000000000000000000..33785d44c5ce467821f63d00cd8f7803a90d251e
--- /dev/null
+++ b/rbtools/api/resource/file_diff.py
@@ -0,0 +1,25 @@
+"""Resource definitions for file diffs.
+
+Version Added:
+    6.0:
+    This was moved from :py:module:`rbtools.api.resource`.
+"""
+
+from __future__ import annotations
+
+from rbtools.api.decorators import request_method_decorator
+from rbtools.api.request import HttpRequest
+from rbtools.api.resource.base import ItemResource, resource_mimetype
+from rbtools.api.resource.mixins import GetPatchMixin
+
+
+@resource_mimetype('application/vnd.reviewboard.org.file')
+class FileDiffResource(GetPatchMixin, ItemResource):
+    """The File Diff resource specific base class."""
+
+    @request_method_decorator
+    def get_diff_data(self, **kwargs):
+        """Retrieves the actual raw diff data for the file."""
+        return HttpRequest(self._url, query_args=kwargs, headers={
+            'Accept': 'application/vnd.reviewboard.org.diff.data+json',
+        })
diff --git a/rbtools/api/resource/mixins.py b/rbtools/api/resource/mixins.py
new file mode 100644
index 0000000000000000000000000000000000000000..8e87d635e03739998484d1bc80494716629663ec
--- /dev/null
+++ b/rbtools/api/resource/mixins.py
@@ -0,0 +1,63 @@
+"""Mixins for API resources.
+
+Version Added:
+    6.0:
+    This was moved from :py:module:`rbtools.api.resource`.
+"""
+
+from __future__ import annotations
+
+from rbtools.api.decorators import request_method_decorator
+from rbtools.api.request import HttpRequest
+
+
+class DiffUploaderMixin:
+    """A mixin for uploading diffs to a resource."""
+
+    def prepare_upload_diff_request(self, diff, parent_diff=None,
+                                    base_dir=None, base_commit_id=None,
+                                    **kwargs):
+        """Create a request that can be used to upload a diff.
+
+        The diff and parent_diff arguments should be strings containing the
+        diff output.
+        """
+        request = HttpRequest(self._url, method='POST', query_args=kwargs)
+        request.add_file('path', 'diff', diff)
+
+        if parent_diff:
+            request.add_file('parent_diff_path', 'parent_diff', parent_diff)
+
+        if base_dir:
+            request.add_field('basedir', base_dir)
+
+        if base_commit_id:
+            request.add_field('base_commit_id', base_commit_id)
+
+        return request
+
+
+class GetPatchMixin:
+    """Mixin for resources that implement a get_patch method.
+
+    Version Added:
+        4.2
+    """
+
+    @request_method_decorator
+    def get_patch(self, **kwargs):
+        """Retrieve the diff file contents.
+
+        Args:
+            **kwargs (dict):
+                Query args to pass to
+                :py:meth:`~rbtools.api.request.HttpRequest.__init__`.
+
+        Returns:
+            ItemResource:
+            A resource payload whose :py:attr:`~ItemResource.data` attribute is
+            the requested patch.
+        """
+        return HttpRequest(self._url, query_args=kwargs, headers={
+            'Accept': 'text/x-patch',
+        })
diff --git a/rbtools/api/resource/review_request.py b/rbtools/api/resource/review_request.py
new file mode 100644
index 0000000000000000000000000000000000000000..c302119aad20947de8df4ffacaedaf98c652746d
--- /dev/null
+++ b/rbtools/api/resource/review_request.py
@@ -0,0 +1,134 @@
+"""Resource definitions for review requests.
+
+Version Added:
+    6.0:
+    This was moved from :py:module:`rbtools.api.resource`.
+"""
+
+from __future__ import annotations
+
+from collections import defaultdict, deque
+from urllib.parse import urljoin
+
+from rbtools.api.decorators import request_method_decorator
+from rbtools.api.resource.base import ItemResource, resource_mimetype
+from rbtools.utils.graphs import path_exists
+
+
+@resource_mimetype('application/vnd.reviewboard.org.review-request')
+class ReviewRequestResource(ItemResource):
+    """The Review Request resource specific base class."""
+
+    @property
+    def absolute_url(self):
+        """Returns the absolute URL for the Review Request.
+
+        The value of absolute_url is returned if it's defined.
+        Otherwise the absolute URL is generated and returned.
+        """
+        if 'absolute_url' in self._fields:
+            return self._fields['absolute_url']
+        else:
+            base_url = self._url.split('/api/')[0]
+            return urljoin(base_url, self.url)
+
+    @property
+    def url(self):
+        """Returns the relative URL to the Review Request.
+
+        The value of 'url' is returned if it's defined. Otherwise, a relative
+        URL is generated and returned.
+
+        This provides compatibility with versions of Review Board older
+        than 1.7.8, which do not have a 'url' field.
+        """
+        return self._fields.get('url', '/r/%s/' % self.id)
+
+    @request_method_decorator
+    def submit(self, description=None, changenum=None):
+        """Submit a review request"""
+        data = {
+            'status': 'submitted',
+        }
+
+        if description:
+            data['description'] = description
+
+        if changenum:
+            data['changenum'] = changenum
+
+        return self.update(data=data, internal=True)
+
+    @request_method_decorator
+    def get_or_create_draft(self, **kwargs):
+        request = self.get_draft(internal=True)
+        request.method = 'POST'
+
+        for name, value in kwargs.items():
+            request.add_field(name, value)
+
+        return request
+
+    def build_dependency_graph(self):
+        """Build the dependency graph for the review request.
+
+        Only review requests in the same repository as this one will be in the
+        graph.
+
+        A ValueError is raised if the graph would contain cycles.
+        """
+        def get_url(resource):
+            """Get the URL of the resource."""
+            if hasattr(resource, 'href'):
+                return resource.href
+            else:
+                return resource.absolute_url
+
+        # Even with the API cache, we don't want to be making more requests
+        # than necessary. The review request resource will be cached by an
+        # ETag, so there will still be a round trip if we don't cache them
+        # here.
+        review_requests_by_url = {}
+        review_requests_by_url[self.absolute_url] = self
+
+        def get_review_request_resource(resource):
+            url = get_url(resource)
+
+            if url not in review_requests_by_url:
+                review_requests_by_url[url] = resource.get(expand='repository')
+
+            return review_requests_by_url[url]
+
+        repository = self.get_repository()
+
+        graph = defaultdict(set)
+
+        visited = set()
+
+        unvisited = deque()
+        unvisited.append(self)
+
+        while unvisited:
+            head = unvisited.popleft()
+
+            if head in visited:
+                continue
+
+            visited.add(get_url(head))
+
+            for tail in head.depends_on:
+                tail = get_review_request_resource(tail)
+
+                if path_exists(graph, tail.id, head.id):
+                    raise ValueError('Circular dependencies.')
+
+                # We don't want to include review requests for other
+                # repositories, so we'll stop if we reach one. We also don't
+                # want to re-land submitted review requests.
+                if (repository.id == tail.repository.id and
+                    tail.status != 'submitted'):
+                    graph[head].add(tail)
+                    unvisited.append(tail)
+
+        graph.default_factory = None
+        return graph
diff --git a/rbtools/api/resource/root.py b/rbtools/api/resource/root.py
new file mode 100644
index 0000000000000000000000000000000000000000..d86f405b4338be8539bd119fe75e6fd2b95a728a
--- /dev/null
+++ b/rbtools/api/resource/root.py
@@ -0,0 +1,80 @@
+"""Resource definitions for the API root.
+
+Version Added:
+    6.0:
+    This was moved from :py:module:`rbtools.api.resource`.
+"""
+
+from __future__ import annotations
+
+import re
+
+from packaging.version import parse as parse_version
+
+from rbtools.api.cache import MINIMUM_VERSION
+from rbtools.api.decorators import request_method_decorator
+from rbtools.api.request import HttpRequest
+from rbtools.api.resource.base import (
+    ItemResource,
+    ResourceDictField,
+    resource_mimetype,
+)
+
+
+@resource_mimetype('application/vnd.reviewboard.org.root')
+class RootResource(ItemResource):
+    """The Root resource specific base class.
+
+    Provides additional methods for fetching any resource directly
+    using the uri templates. A method of the form "get_<uri-template-name>"
+    is called to retrieve the HttpRequest corresponding to the
+    resource. Template replacement values should be passed in as a
+    dictionary to the values parameter.
+    """
+
+    #: Capabilities for the Review Board server.
+    capabilities: ResourceDictField
+
+    _excluded_attrs = ['uri_templates']
+    _TEMPLATE_PARAM_RE = re.compile(r'\{(?P<key>[A-Za-z_0-9]*)\}')
+
+    def __init__(self, transport, payload, url, **kwargs):
+        super(RootResource, self).__init__(transport, payload, url, token=None)
+        # Generate methods for accessing resources directly using
+        # the uri-templates.
+        for name, url in payload['uri_templates'].items():
+            attr_name = 'get_%s' % name
+
+            if not hasattr(self, attr_name):
+                setattr(self,
+                        attr_name,
+                        lambda resource=self, url=url, **kwargs: (
+                            self._get_template_request(url, **kwargs)))
+
+        server_version = payload.get('product', {}).get('package_version')
+
+        if (server_version is None or
+            parse_version(server_version) < parse_version(MINIMUM_VERSION)):
+            # This version is too old to safely support caching (there were
+            # bugs before this version). Disable caching.
+            transport.disable_cache()
+
+    @request_method_decorator
+    def _get_template_request(self, url_template, values={}, **kwargs):
+        """Generate an HttpRequest from a uri-template.
+
+        This will replace each '{variable}' in the template with the
+        value from kwargs['variable'], or if it does not exist, the
+        value from values['variable']. The resulting url is used to
+        create an HttpRequest.
+        """
+        def get_template_value(m):
+            try:
+                return str(kwargs.pop(m.group('key'), None) or
+                           values[m.group('key')])
+            except KeyError:
+                raise ValueError('Template was not provided a value for "%s"' %
+                                 m.group('key'))
+
+        url = self._TEMPLATE_PARAM_RE.sub(get_template_value, url_template)
+        return HttpRequest(url, query_args=kwargs)
diff --git a/rbtools/api/resource/screenshot.py b/rbtools/api/resource/screenshot.py
new file mode 100644
index 0000000000000000000000000000000000000000..8c39bcb9d1282bd278e2f2f1bbf6c58aab535e71
--- /dev/null
+++ b/rbtools/api/resource/screenshot.py
@@ -0,0 +1,32 @@
+"""Resource definitions for screenshots.
+
+Version Added:
+    6.0:
+    This was moved from :py:module:`rbtools.api.resource`.
+"""
+
+from __future__ import annotations
+
+from rbtools.api.decorators import request_method_decorator
+from rbtools.api.request import HttpRequest
+from rbtools.api.resource.base import ListResource, resource_mimetype
+
+
+@resource_mimetype('application/vnd.reviewboard.org.screenshots')
+class ScreenshotListResource(ListResource):
+    """The Screenshot List resource specific base class."""
+
+    @request_method_decorator
+    def upload_screenshot(self, filename, content, caption=None, **kwargs):
+        """Uploads a new screenshot.
+
+        The content argument should contain the body of the screenshot
+        to be uploaded, in string format.
+        """
+        request = HttpRequest(self._url, method='POST', query_args=kwargs)
+        request.add_file('path', filename, content)
+
+        if caption:
+            request.add_field('caption', caption)
+
+        return request
diff --git a/rbtools/api/resource/validate_diff.py b/rbtools/api/resource/validate_diff.py
new file mode 100644
index 0000000000000000000000000000000000000000..c5942c4ddd40d86fa34ba84c9b4b3f167d24075c
--- /dev/null
+++ b/rbtools/api/resource/validate_diff.py
@@ -0,0 +1,38 @@
+"""Resource definitions for diff validation.
+
+Version Added:
+    6.0:
+    This was moved from :py:module:`rbtools.api.resource`.
+"""
+
+from __future__ import annotations
+
+from rbtools.api.decorators import request_method_decorator
+from rbtools.api.resource.base import ItemResource, resource_mimetype
+from rbtools.api.resource.mixins import DiffUploaderMixin
+
+
+@resource_mimetype('application/vnd.reviewboard.org.diff-validation')
+class ValidateDiffResource(DiffUploaderMixin, ItemResource):
+    """The Validate Diff resource specific base class.
+
+    Provides additional functionality to assist in the validation of diffs.
+    """
+
+    @request_method_decorator
+    def validate_diff(self, repository, diff, parent_diff=None, base_dir=None,
+                      base_commit_id=None, **kwargs):
+        """Validate a diff."""
+        request = self.prepare_upload_diff_request(
+            diff,
+            parent_diff=parent_diff,
+            base_dir=base_dir,
+            base_commit_id=base_commit_id,
+            **kwargs)
+
+        request.add_field('repository', repository)
+
+        if base_commit_id:
+            request.add_field('base_commit_id', base_commit_id)
+
+        return request
diff --git a/rbtools/api/resource/validate_diff_commit.py b/rbtools/api/resource/validate_diff_commit.py
new file mode 100644
index 0000000000000000000000000000000000000000..77690232aa00ce8d41667f1d9bda5a5f9a4c3b3d
--- /dev/null
+++ b/rbtools/api/resource/validate_diff_commit.py
@@ -0,0 +1,69 @@
+"""Resource definitions for diff commit validation.
+
+Version Added:
+    6.0:
+    This was moved from :py:module:`rbtools.api.resource`.
+"""
+
+from __future__ import annotations
+
+from rbtools.api.decorators import request_method_decorator
+from rbtools.api.request import HttpRequest
+from rbtools.api.resource.base import ItemResource, resource_mimetype
+
+
+@resource_mimetype('application/vnd.reviewboard.org.commit-validation')
+class ValidateDiffCommitResource(ItemResource):
+    """The commit validation resource specific base class."""
+
+    @request_method_decorator
+    def validate_commit(self, repository, diff, commit_id, parent_id,
+                        parent_diff=None, base_commit_id=None,
+                        validation_info=None, **kwargs):
+        """Validate the diff for a commit.
+
+        Args:
+            repository (unicode):
+                The name of the repository.
+
+            diff (bytes):
+                The contents of the diff to validate.
+
+            commit_id (unicode):
+                The ID of the commit being validated.
+
+            parent_id (unicode):
+                The ID of the parent commit.
+
+            parent_diff (bytes, optional):
+                The contents of the parent diff.
+
+            base_commit_id (unicode, optional):
+                The base commit ID.
+
+            validation_info (unicode, optional):
+                Validation information from a previous call to this resource.
+
+            **kwargs (dict):
+                Keyword arguments used to build the querystring.
+
+        Returns:
+            ValidateDiffCommitResource:
+            The validation result.
+        """
+        request = HttpRequest(self._url, method='POST', query_args=kwargs)
+        request.add_file('diff', 'diff', diff)
+        request.add_field('repository', repository)
+        request.add_field('commit_id', commit_id)
+        request.add_field('parent_id', parent_id)
+
+        if parent_diff:
+            request.add_file('parent_diff', 'parent_diff', parent_diff)
+
+        if base_commit_id:
+            request.add_field('base_commit_id', base_commit_id)
+
+        if validation_info:
+            request.add_field('validation_info', validation_info)
+
+        return request
diff --git a/rbtools/api/tests/test_resource.py b/rbtools/api/tests/test_resource.py
index 455aa1990f8f9f28e748badb08640cb841cc8410..f7eb90f01b4406a9529540832149432a0b4702a8 100644
--- a/rbtools/api/tests/test_resource.py
+++ b/rbtools/api/tests/test_resource.py
@@ -13,8 +13,8 @@ from rbtools.api.resource import (CountResource,
                                   ResourceExtraDataField,
                                   ResourceLinkField,
                                   ResourceListField,
-                                  RootResource,
-                                  _EXTRA_DATA_DOCS_URL)
+                                  RootResource)
+from rbtools.api.resource.base import _EXTRA_DATA_DOCS_URL
 from rbtools.api.tests.base import TestWithPayloads
 
 
