diff --git a/CHANGELOG.md b/CHANGELOG.md
index e43e850d..ca463de5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -35,6 +35,10 @@ This release is not backwards compatible. For easy migration best upgrade first
 * Avoid printing invalid pointer when api returns 404
 
 
+### Added
+
+* Add support for DJANGO REST Framework 3.10.
+
 ## [2.8.0] - 2019-06-13
 
 This is the last release supporting Python 2.7, Python 3.4, Django Filter 1.1, Django REST Framework <=3.8 and Django 2.0.
diff --git a/example/requirements.txt b/example/requirements.txt
index 2f4213ee..65e13bd1 100644
--- a/example/requirements.txt
+++ b/example/requirements.txt
@@ -10,3 +10,5 @@ pyparsing
 pytz
 sqlparse
 django-filter>=2.0
+PyYAML>=5.1.1
+coreapi>=2.3.3
diff --git a/example/settings/dev.py b/example/settings/dev.py
index ade24139..e512d187 100644
--- a/example/settings/dev.py
+++ b/example/settings/dev.py
@@ -1,5 +1,9 @@
 import os
 
+from rest_framework import VERSION as DRFVERSION
+
+drf_version = tuple(int(x) for x in DRFVERSION.split('.'))
+
 SITE_ID = 1
 DEBUG = True
 
@@ -21,6 +25,7 @@
     'django.contrib.sites',
     'django.contrib.sessions',
     'django.contrib.auth',
+    'rest_framework_json_api',
     'rest_framework',
     'polymorphic',
     'example',
@@ -99,3 +104,6 @@
     ),
     'TEST_REQUEST_DEFAULT_FORMAT': 'vnd.api+json'
 }
+
+if drf_version >= (3, 10):
+    REST_FRAMEWORK['DEFAULT_SCHEMA_CLASS'] = 'rest_framework_json_api.schemas.openapi.AutoSchema'
diff --git a/example/tests/test_openapi.py b/example/tests/test_openapi.py
new file mode 100644
index 00000000..a9ffbfe3
--- /dev/null
+++ b/example/tests/test_openapi.py
@@ -0,0 +1,857 @@
+# largely based on DRF's test_openapi
+import pytest
+from django.conf.urls import url
+from django.test import RequestFactory, TestCase, override_settings
+from rest_framework import VERSION as DRFVERSION
+from rest_framework.request import Request
+
+from example import views
+
+try:
+    from rest_framework_json_api.schemas.openapi import AutoSchema, SchemaGenerator
+except ImportError:
+    AutoSchema = SchemaGenerator = None
+
+
+drf_version = tuple(int(x) for x in DRFVERSION.split('.'))
+pytestmark = pytest.mark.skipif(drf_version < (3, 10), reason="requires DRF 3.10 or higher")
+
+
+def create_request(path):
+    factory = RequestFactory()
+    request = Request(factory.get(path))
+    return request
+
+
+def create_view(view_cls, method, request):
+    generator = SchemaGenerator()
+    view = generator.create_view(view_cls.as_view(), method, request)
+    return view
+
+
+def create_view_with_kw(view_cls, method, request, initkwargs):
+    generator = SchemaGenerator()
+    view = generator.create_view(view_cls.as_view(initkwargs), method, request)
+    return view
+
+
+class TestOperationIntrospection(TestCase):
+
+    def test_path_without_parameters(self):
+        path = '/authors/'
+        method = 'GET'
+
+        view = create_view_with_kw(
+            views.AuthorViewSet,
+            method,
+            create_request(path),
+            {'get': 'list'}
+        )
+        inspector = AutoSchema()
+        inspector.view = view
+
+        operation = inspector.get_operation(path, method)
+        # TODO: pick and choose portions rather than comparing the whole thing?
+        assert operation == {
+            'operationId': 'List/authors/',
+            'security': [{'basicAuth': []}],
+            'parameters': [
+                {'$ref': '#/components/parameters/include'},
+                {'$ref': '#/components/parameters/fields'},
+                {'$ref': '#/components/parameters/sort'},
+                {'name': 'page[number]', 'required': False, 'in': 'query',
+                 'description': 'A page number within the paginated result set.',
+                 'schema': {'type': 'integer'}},
+                {'name': 'page[size]', 'required': False, 'in': 'query',
+                 'description': 'Number of results to return per page.',
+                 'schema': {'type': 'integer'}},
+                {'name': 'sort', 'required': False, 'in': 'query',
+                 'description': 'Which field to use when ordering the results.',
+                 'schema': {'type': 'string'}},
+                {'name': 'filter[search]', 'required': False, 'in': 'query',
+                 'description': 'A search term.', 'schema': {'type': 'string'}}
+            ],
+            'responses': {
+                '200': {
+                    'description': 'List/authors/',
+                    'content': {
+                        'application/vnd.api+json': {
+                            'schema': {
+                                'type': 'object',
+                                'required': ['data'],
+                                'properties': {
+                                    'data': {
+                                        'type': 'array',
+                                        'items': {
+                                            'type': 'object',
+                                            'required': ['type', 'id'],
+                                            'additionalProperties': False,
+                                            'properties': {
+                                                'type': {'$ref': '#/components/schemas/type'},
+                                                'id': {'$ref': '#/components/schemas/id'},
+                                                'links': {
+                                                    'type': 'object',
+                                                    'properties': {
+                                                        'self': {
+                                                            '$ref': '#/components/schemas/link'
+                                                        }
+                                                    }
+                                                },
+                                                'attributes': {
+                                                    'type': 'object',
+                                                    'properties': {
+                                                        'name': {
+                                                            'type': 'string',
+                                                            'maxLength': 50
+                                                        },
+                                                        'email': {
+                                                            'type': 'string',
+                                                            'format': 'email',
+                                                            'maxLength': 254
+                                                        }
+                                                    },
+                                                    'required': ['name', 'email']
+                                                },
+                                                'relationships': {
+                                                    'type': 'object',
+                                                    'properties': {
+                                                        'bio': {
+                                                            '$ref':
+                                                                '#/components/schemas/reltoone'
+                                                        },
+                                                        'entries': {
+                                                            '$ref':
+                                                                '#/components/schemas/reltomany'
+                                                        },
+                                                        'comments': {
+                                                            '$ref':
+                                                                '#/components/schemas/reltomany'
+                                                        },
+                                                        'first_entry': {
+                                                            '$ref':
+                                                                '#/components/schemas/reltoone'
+                                                        },
+                                                        'type': {
+                                                            '$ref': '#/components/schemas/reltoone'
+                                                        }
+                                                    }
+                                                }
+                                            }
+                                        }
+                                    },
+                                    'included': {
+                                        'type': 'array',
+                                        'uniqueItems': True,
+                                        'items': {'$ref': '#/components/schemas/resource'}
+                                    },
+                                    'links': {
+                                        'description': 'Link members related to primary data',
+                                        'allOf': [
+                                            {'$ref': '#/components/schemas/links'},
+                                            {'$ref': '#/components/schemas/pagination'}
+                                        ]
+                                    },
+                                    'jsonapi': {
+                                        '$ref': '#/components/schemas/jsonapi'
+                                    }
+                                }
+                            }
+                        }
+                    }
+                },
+                '401': {
+                    'description': 'not authorized',
+                    'content': {
+                        'application/vnd.api+json': {
+                            'schema': {'$ref': '#/components/schemas/failure'}
+                        }
+                    }
+                },
+                '404': {
+                    'description': 'not found',
+                    'content': {
+                        'application/vnd.api+json': {
+                            'schema': {'$ref': '#/components/schemas/failure'}
+                        }
+                    }
+                }
+            }
+        }
+
+    def test_path_with_id_parameter(self):
+        path = '/authors/{id}/'
+        method = 'GET'
+
+        view = create_view_with_kw(
+            views.AuthorViewSet,
+            method,
+            create_request(path),
+            {'get': 'retrieve'}
+        )
+        inspector = AutoSchema()
+        inspector.view = view
+
+        operation = inspector.get_operation(path, method)
+        assert operation == {
+            'operationId': 'retrieve/authors/{id}/',
+            'security': [{'basicAuth': []}],
+            'parameters': [
+                {
+                    'name': 'id',
+                    'in': 'path',
+                    'required': True,
+                    'description': 'A unique integer value identifying this author.',
+                    'schema': {'type': 'string'}
+                },
+                {'$ref': '#/components/parameters/include'},
+                {'$ref': '#/components/parameters/fields'},
+                {'$ref': '#/components/parameters/sort'},
+                {
+                    'name': 'sort', 'required': False, 'in': 'query',
+                    'description': 'Which field to use when ordering the results.',
+                    'schema': {'type': 'string'}
+                },
+                {
+                    'name': 'filter[search]', 'required': False, 'in': 'query',
+                    'description': 'A search term.',
+                    'schema': {'type': 'string'}
+                }
+            ],
+            'responses': {
+                '200': {
+                    'description': 'retrieve/authors/{id}/',
+                    'content': {
+                        'application/vnd.api+json': {
+                            'schema': {
+                                'type': 'object',
+                                'required': ['data'],
+                                'properties': {
+                                    'data': {
+                                        'type': 'object',
+                                        'required': ['type', 'id'],
+                                        'additionalProperties': False,
+                                        'properties': {
+                                            'type': {'$ref': '#/components/schemas/type'},
+                                            'id': {'$ref': '#/components/schemas/id'},
+                                            'links': {
+                                                'type': 'object',
+                                                'properties': {
+                                                    'self': {'$ref': '#/components/schemas/link'}
+                                                }
+                                            },
+                                            'attributes': {
+                                                'type': 'object',
+                                                'properties': {
+                                                    'name': {
+                                                        'type': 'string',
+                                                        'maxLength': 50
+                                                    },
+                                                    'email': {
+                                                        'type': 'string',
+                                                        'format': 'email',
+                                                        'maxLength': 254
+                                                    }
+                                                },
+                                                'required': ['name', 'email']
+                                            },
+                                            'relationships': {
+                                                'type': 'object',
+                                                'properties': {
+                                                    'bio': {
+                                                        '$ref': '#/components/schemas/reltoone'
+                                                    },
+                                                    'entries': {
+                                                        '$ref': '#/components/schemas/reltomany'
+                                                    },
+                                                    'comments': {
+                                                        '$ref': '#/components/schemas/reltomany'
+                                                    },
+                                                    'first_entry': {
+                                                        '$ref': '#/components/schemas/reltoone'
+                                                    },
+                                                    'type': {
+                                                        '$ref': '#/components/schemas/reltoone'
+                                                    }
+                                                }
+                                            }
+                                        }
+                                    },
+                                    'included': {
+                                        'type': 'array',
+                                        'uniqueItems': True,
+                                        'items': {'$ref': '#/components/schemas/resource'}
+                                    },
+                                    'links': {
+                                        'description': 'Link members related to primary data',
+                                        'allOf': [
+                                            {'$ref': '#/components/schemas/links'},
+                                            {'$ref': '#/components/schemas/pagination'}
+                                        ]
+                                    },
+                                    'jsonapi': {'$ref': '#/components/schemas/jsonapi'}
+                                }
+                            }
+                        }
+                    }
+                },
+                '401': {
+                    'description': 'not authorized',
+                    'content': {
+                        'application/vnd.api+json': {
+                            'schema': {'$ref': '#/components/schemas/failure'}
+                        }
+                    }
+                },
+                '404': {
+                    'description': 'not found',
+                    'content': {
+                        'application/vnd.api+json': {
+                            'schema': {'$ref': '#/components/schemas/failure'}
+                        }
+                    }
+                }
+            }
+        }
+
+    def test_post_request(self):
+        method = 'POST'
+        path = '/authors/'
+
+        view = create_view_with_kw(
+            views.AuthorViewSet,
+            method,
+            create_request(path),
+            {'post': 'create'}
+        )
+        inspector = AutoSchema()
+        inspector.view = view
+
+        operation = inspector.get_operation(path, method)
+        assert operation == {
+            'operationId': 'create/authors/',
+            'security': [{'basicAuth': []}],
+            'parameters': [],
+            'requestBody': {
+                'content': {
+                    'application/vnd.api+json': {
+                        'schema': {
+                            'required': ['data'],
+                            'properties': {
+                                'data': {
+                                    'type': 'object',
+                                    'required': ['type'],
+                                    'additionalProperties': False,
+                                    'properties': {
+                                        'type': {'$ref': '#/components/schemas/type'},
+                                        'id': {'$ref': '#/components/schemas/id'},
+                                        'links': {
+                                            'type': 'object',
+                                            'properties': {
+                                                'self': {'$ref': '#/components/schemas/link'}
+                                            }
+                                        },
+                                        'attributes': {
+                                            'type': 'object',
+                                            'properties': {
+                                                'name': {
+                                                    'type': 'string',
+                                                    'maxLength': 50
+                                                },
+                                                'email': {
+                                                    'type': 'string',
+                                                    'format': 'email',
+                                                    'maxLength': 254}
+                                            },
+                                            'required': ['name', 'email']
+                                        },
+                                        'relationships': {
+                                            'type': 'object',
+                                            'properties': {
+                                                'bio': {
+                                                    '$ref': '#/components/schemas/reltoone'
+                                                },
+                                                'entries': {
+                                                    '$ref': '#/components/schemas/reltomany'
+                                                },
+                                                'comments': {
+                                                    '$ref': '#/components/schemas/reltomany'
+                                                },
+                                                'first_entry': {
+                                                    '$ref': '#/components/schemas/reltoone'
+                                                }, 'type': {
+                                                    '$ref': '#/components/schemas/reltoone'
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            },
+            'responses': {
+                '201': {
+                    'description':
+                        '[Created](https://jsonapi.org/format/#crud-creating-responses-201). '
+                        'Assigned `id` and/or any other changes are in this response.',
+                    'content': {
+                        'application/vnd.api+json': {
+                            'schema': {
+                                'type': 'object',
+                                'required': ['data'],
+                                'properties': {
+                                    'data': {
+                                        'type': 'object',
+                                        'required': ['type', 'id'],
+                                        'additionalProperties': False,
+                                        'properties': {
+                                            'type': {'$ref': '#/components/schemas/type'},
+                                            'id': {'$ref': '#/components/schemas/id'},
+                                            'links': {
+                                                'type': 'object',
+                                                'properties': {
+                                                    'self': {'$ref': '#/components/schemas/link'}
+                                                }
+                                            },
+                                            'attributes': {
+                                                'type': 'object',
+                                                'properties': {
+                                                    'name': {
+                                                        'type': 'string',
+                                                        'maxLength': 50
+                                                    },
+                                                    'email': {
+                                                        'type': 'string',
+                                                        'format': 'email',
+                                                        'maxLength': 254
+                                                    }
+                                                },
+                                                'required': ['name', 'email']
+                                            },
+                                            'relationships': {
+                                                'type': 'object',
+                                                'properties': {
+                                                    'bio': {
+                                                        '$ref': '#/components/schemas/reltoone'
+                                                    },
+                                                    'entries': {
+                                                        '$ref': '#/components/schemas/reltomany'
+                                                    },
+                                                    'comments': {
+                                                        '$ref': '#/components/schemas/reltomany'
+                                                    },
+                                                    'first_entry': {
+                                                        '$ref': '#/components/schemas/reltoone'
+                                                    },
+                                                    'type': {
+                                                        '$ref': '#/components/schemas/reltoone'
+                                                    }
+                                                }
+                                            }
+                                        }
+                                    },
+                                    'included': {
+                                        'type': 'array',
+                                        'uniqueItems': True,
+                                        'items': {'$ref': '#/components/schemas/resource'}
+                                    },
+                                    'links': {
+                                        'description': 'Link members related to primary data',
+                                        'allOf': [
+                                            {'$ref': '#/components/schemas/links'},
+                                            {'$ref': '#/components/schemas/pagination'}
+                                        ]
+                                    },
+                                    'jsonapi': {
+                                        '$ref': '#/components/schemas/jsonapi'
+                                    }
+                                }
+                            }
+                        }
+                    }
+                },
+                '202': {
+                    'description': 'Accepted for [asynchronous processing]'
+                                   '(https://jsonapi.org/recommendations/#asynchronous-processing)',
+                    'content': {
+                        'application/vnd.api+json': {
+                            'schema': {'$ref': '#/components/schemas/datum'}
+                        }
+                    }
+                },
+                '204': {
+                    'description': '[Created](https://jsonapi.org/format/'
+                                   '#crud-creating-responses-204) with the supplied `id`. '
+                                   'No other changes from what was POSTed.'
+                },
+                '401': {
+                    'description': 'not authorized',
+                    'content': {
+                        'application/vnd.api+json': {
+                            'schema': {'$ref': '#/components/schemas/failure'}
+                        }
+                    }
+                },
+                '403': {
+                    'description':
+                        '[Forbidden](https://jsonapi.org/format/#crud-creating-responses-403)',
+                    'content': {
+                        'application/vnd.api+json': {
+                            'schema': {'$ref': '#/components/schemas/failure'}
+                        }
+                    }
+                },
+                '404': {
+                    'description': '[Related resource does not exist]'
+                                   '(https://jsonapi.org/format/#crud-creating-responses-404)',
+                    'content': {
+                        'application/vnd.api+json': {
+                            'schema': {'$ref': '#/components/schemas/failure'}
+                        }
+                    }
+                },
+                '409': {
+                    'description':
+                        '[Conflict](https://jsonapi.org/format/#crud-creating-responses-409)',
+                    'content': {
+                        'application/vnd.api+json': {
+                            'schema': {'$ref': '#/components/schemas/failure'}
+                        }
+                    }
+                }
+            }
+        }
+
+    def test_patch_request(self):
+        method = 'PATCH'
+        path = '/authors/{id}'
+
+        view = create_view_with_kw(
+            views.AuthorViewSet,
+            method,
+            create_request(path),
+            {'patch': 'update'}
+        )
+        inspector = AutoSchema()
+        inspector.view = view
+
+        operation = inspector.get_operation(path, method)
+        assert operation == {
+            'operationId': 'update/authors/{id}',
+            'security': [{'basicAuth': []}],
+            'parameters': [
+                {
+                    'name': 'id',
+                    'in': 'path',
+                    'required': True,
+                    'description': 'A unique integer value identifying this author.',
+                    'schema': {'type': 'string'}
+                }
+            ],
+            'requestBody': {
+                'content': {
+                    'application/vnd.api+json': {
+                        'schema': {
+                            'required': ['data'],
+                            'properties': {
+                                'data': {
+                                    'type': 'object',
+                                    'required': ['type', 'id'],
+                                    'additionalProperties': False,
+                                    'properties': {
+                                        'type': {'$ref': '#/components/schemas/type'},
+                                        'id': {'$ref': '#/components/schemas/id'},
+                                        'links': {
+                                            'type': 'object',
+                                            'properties': {
+                                                'self': {'$ref': '#/components/schemas/link'}
+                                            }
+                                        },
+                                        'attributes': {
+                                            'type': 'object',
+                                            'properties': {
+                                                'name': {
+                                                    'type': 'string',
+                                                    'maxLength': 50
+                                                },
+                                                'email': {
+                                                    'type': 'string',
+                                                    'format': 'email',
+                                                    'maxLength': 254
+                                                }
+                                            }
+                                        },
+                                        'relationships': {
+                                            'type': 'object',
+                                            'properties': {
+                                                'bio': {
+                                                    '$ref': '#/components/schemas/reltoone'
+                                                },
+                                                'entries': {
+                                                    '$ref': '#/components/schemas/reltomany'
+                                                },
+                                                'comments': {
+                                                    '$ref': '#/components/schemas/reltomany'
+                                                },
+                                                'first_entry': {
+                                                    '$ref': '#/components/schemas/reltoone'
+                                                },
+                                                'type': {
+                                                    '$ref': '#/components/schemas/reltoone'
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            },
+            'responses': {
+                '200': {
+                    'description': 'update/authors/{id}',
+                    'content': {
+                        'application/vnd.api+json': {
+                            'schema': {
+                                'type': 'object',
+                                'required': ['data'],
+                                'properties': {
+                                    'data': {
+                                        'type': 'object',
+                                        'required': ['type', 'id'],
+                                        'additionalProperties': False,
+                                        'properties': {
+                                            'type': {'$ref': '#/components/schemas/type'},
+                                            'id': {'$ref': '#/components/schemas/id'},
+                                            'links': {
+                                                'type': 'object',
+                                                'properties': {
+                                                    'self': {
+                                                        '$ref': '#/components/schemas/link'
+                                                    }
+                                                }
+                                            },
+                                            'attributes': {
+                                                'type': 'object',
+                                                'properties': {
+                                                    'name': {
+                                                        'type': 'string',
+                                                        'maxLength': 50
+                                                    },
+                                                    'email': {
+                                                        'type': 'string',
+                                                        'format': 'email',
+                                                        'maxLength': 254
+                                                    }
+                                                },
+                                                'required': ['name', 'email']
+                                            },
+                                            'relationships': {
+                                                'type': 'object',
+                                                'properties': {
+                                                    'bio': {
+                                                        '$ref': '#/components/schemas/reltoone'
+                                                    },
+                                                    'entries': {
+                                                        '$ref': '#/components/schemas/reltomany'
+                                                    },
+                                                    'comments': {
+                                                        '$ref': '#/components/schemas/reltomany'
+                                                    },
+                                                    'first_entry': {
+                                                        '$ref': '#/components/schemas/reltoone'
+                                                    },
+                                                    'type': {
+                                                        '$ref': '#/components/schemas/reltoone'
+                                                    }
+                                                }
+                                            }
+                                        }
+                                    },
+                                    'included': {
+                                        'type': 'array',
+                                        'uniqueItems': True,
+                                        'items': {
+                                            '$ref': '#/components/schemas/resource'
+                                        }
+                                    },
+                                    'links': {
+                                        'description':
+                                            'Link members related to primary data',
+                                        'allOf': [
+                                            {'$ref': '#/components/schemas/links'},
+                                            {'$ref': '#/components/schemas/pagination'}
+                                        ]
+                                    },
+                                    'jsonapi': {
+                                        '$ref': '#/components/schemas/jsonapi'
+                                    }
+                                }
+                            }
+                        }
+                    }
+                },
+                '401': {
+                    'description': 'not authorized',
+                    'content': {
+                        'application/vnd.api+json': {
+                            'schema': {'$ref': '#/components/schemas/failure'}
+                        }
+                    }
+                },
+                '403': {
+                    'description':
+                        '[Forbidden](https://jsonapi.org/format/#crud-updating-responses-403)',
+                    'content': {
+                        'application/vnd.api+json': {
+                            'schema': {'$ref': '#/components/schemas/failure'}
+                        }
+                    }
+                },
+                '404': {
+                    'description':
+                        '[Related resource does not exist]'
+                        '(https://jsonapi.org/format/#crud-updating-responses-404)',
+                    'content': {
+                        'application/vnd.api+json': {
+                            'schema': {'$ref': '#/components/schemas/failure'}
+                        }
+                    }
+                },
+                '409': {
+                    'description':
+                        '[Conflict]([Conflict]'
+                        '(https://jsonapi.org/format/#crud-updating-responses-409)',
+                    'content': {
+                        'application/vnd.api+json': {
+                            'schema': {'$ref': '#/components/schemas/failure'}
+                        }
+                    }
+                }
+            }
+        }
+
+    def test_delete_request(self):
+        method = 'DELETE'
+        path = '/authors/{id}'
+
+        view = create_view_with_kw(
+            views.AuthorViewSet,
+            method,
+            create_request(path),
+            {'delete': 'delete'}
+        )
+        inspector = AutoSchema()
+        inspector.view = view
+
+        operation = inspector.get_operation(path, method)
+        assert operation == {
+            'operationId': 'Destroy/authors/{id}',
+            'security': [{'basicAuth': []}],
+            'parameters': [
+                {
+                    'name': 'id',
+                    'in': 'path',
+                    'required': True,
+                    'description': 'A unique integer value identifying this author.',
+                    'schema': {'type': 'string'}
+                }
+            ],
+            'responses': {
+                '200': {
+                    'description':
+                        '[OK](https://jsonapi.org/format/#crud-deleting-responses-200)',
+                    'content': {
+                        'application/vnd.api+json': {
+                            'schema': {'$ref': '#/components/schemas/onlymeta'}
+                        }
+                    }
+                },
+                '202': {
+                    'description':
+                        'Accepted for [asynchronous processing]'
+                        '(https://jsonapi.org/recommendations/#asynchronous-processing)',
+                    'content': {
+                        'application/vnd.api+json': {
+                            'schema': {'$ref': '#/components/schemas/datum'}
+                        }
+                    }
+                },
+                '204': {
+                    'description':
+                        '[no content](https://jsonapi.org/format/#crud-deleting-responses-204)'
+                },
+                '401': {
+                    'description': 'not authorized',
+                    'content': {
+                        'application/vnd.api+json': {
+                            'schema': {'$ref': '#/components/schemas/failure'}
+                        }
+                    }
+                },
+                '404': {
+                    'description':
+                        '[Resource does not exist]'
+                        '(https://jsonapi.org/format/#crud-deleting-responses-404)',
+                    'content': {
+                        'application/vnd.api+json': {
+                            'schema': {'$ref': '#/components/schemas/failure'}
+                        }
+                    }
+                }
+            }
+        }
+
+    # TODO: figure these out
+    # def test_retrieve_relationships(self):
+    #     path = '/authors/{id}/relationships/bio/'
+    #     method = 'GET'
+    #
+    #     view = create_view_with_kw(
+    #         views.AuthorRelationshipView,
+    #         method,
+    #         create_request(path),
+    #         {'get': 'retrieve'}
+    #     )
+    #     inspector = AutoSchema()
+    #     inspector.view = view
+    #
+    #     operation = inspector.get_operation(path, method)
+    #     assert operation == {}
+
+    # def test_retrieve_related(self):
+    #     path = '/authors/{id}/{related_field}/'
+    #     method = 'GET'
+    #
+    #     view = create_view_with_kw(
+    #         views.AuthorViewSet,
+    #         method,
+    #         create_request(path),
+    #         {'get': 'retrieve_related',
+    #          'related_field': 'bio'}
+    #     )
+    #     inspector = AutoSchema()
+    #     inspector.view = view
+    #
+    #     operation = inspector.get_operation(path, method)
+    #     assert operation == {}
+
+@override_settings(
+    REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework_json_api.schemas.openapi.AutoSchema'})
+class TestGenerator(TestCase):
+    def test_schema_construction(self):
+        """Construction of the top level dictionary."""
+        patterns = [
+            url(r'^authors/?$', views.AuthorViewSet.as_view({'get': 'list'})),
+        ]
+        generator = SchemaGenerator(patterns=patterns)
+
+        request = create_request('/')
+        schema = generator.get_schema(request=request)
+
+        assert 'openapi' in schema
+        assert 'info' in schema
+        assert 'paths' in schema
+        assert 'components' in schema
diff --git a/example/tests/unit/test_filter_schema_params.py b/example/tests/unit/test_filter_schema_params.py
new file mode 100644
index 00000000..2352a59b
--- /dev/null
+++ b/example/tests/unit/test_filter_schema_params.py
@@ -0,0 +1,76 @@
+import pytest
+from rest_framework import VERSION as DRFVERSION
+from rest_framework import filters as drf_filters
+
+from rest_framework_json_api import filters as dja_filters
+from rest_framework_json_api.django_filters import backends
+
+from example.views import EntryViewSet
+
+
+class DummyEntryViewSet(EntryViewSet):
+    filter_backends = (dja_filters.QueryParameterValidationFilter, dja_filters.OrderingFilter,
+                       backends.DjangoFilterBackend, drf_filters.SearchFilter)
+    filterset_fields = {
+        'id': ('exact',),
+        'headline': ('exact',),
+    }
+
+    def __init__(self):
+        # dummy up self.request since PreloadIncludesMixin expects it to be defined
+        self.request = None
+
+
+#  get_schema_operation_parameters is only available in DRF >= 3.10
+drf_version = tuple(int(x) for x in DRFVERSION.split('.'))
+pytestmark = pytest.mark.skipif(drf_version < (3, 10), reason="requires DRF 3.10 or higher")
+
+
+def test_filters_get_schema_params():
+    """
+    test all my filters for `get_schema_operation_parameters()`
+    """
+    # list of tuples: (filter, expected result)
+    filters = [
+        (dja_filters.QueryParameterValidationFilter, []),
+        (backends.DjangoFilterBackend, [
+            {
+                'name': 'filter[id]', 'required': False, 'in': 'query',
+                'description': 'id', 'schema': {'type': 'string'}
+            },
+            {
+                'name': 'filter[headline]', 'required': False, 'in': 'query',
+                'description': 'headline', 'schema': {'type': 'string'}
+            }
+        ]),
+        (dja_filters.OrderingFilter, [
+            {
+                'name': 'sort', 'required': False, 'in': 'query',
+                'description': 'Which field to use when ordering the results.',
+                'schema': {'type': 'string'}
+            }
+        ]),
+        (drf_filters.SearchFilter, [
+            {
+                'name': 'filter[search]', 'required': False, 'in': 'query',
+                'description': 'A search term.',
+                'schema': {'type': 'string'}
+            }
+        ]),
+    ]
+    view = DummyEntryViewSet()
+
+    for c, expected in filters:
+        f = c()
+        result = f.get_schema_operation_parameters(view)
+        assert len(result) == len(expected)
+        if len(result) == 0:
+            return
+        # py35: the result list/dict ordering isn't guaranteed
+        for res_item in result:
+            assert 'name' in res_item
+            for exp_item in expected:
+                if res_item['name'] == exp_item['name']:
+                    assert res_item == exp_item
+                    return
+        assert False
diff --git a/requirements-development.txt b/requirements-development.txt
index 26472791..ae0913d6 100644
--- a/requirements-development.txt
+++ b/requirements-development.txt
@@ -15,3 +15,5 @@ recommonmark==0.6.0
 Sphinx==2.1.2
 sphinx_rtd_theme==0.4.3
 twine==1.13.0
+django-oauth-toolkit==1.2.0
+oauthlib==2.1.0
diff --git a/rest_framework_json_api/django_filters/backends.py b/rest_framework_json_api/django_filters/backends.py
index 0c4b80d3..6ea11bd7 100644
--- a/rest_framework_json_api/django_filters/backends.py
+++ b/rest_framework_json_api/django_filters/backends.py
@@ -122,3 +122,10 @@ def get_filterset_kwargs(self, request, queryset, view):
             'request': request,
             'filter_keys': filter_keys,
         }
+
+    def get_schema_operation_parameters(self, view):
+        # wrap query parameters in 'filter[{}]'.format(name)
+        result = super(DjangoFilterBackend, self).get_schema_operation_parameters(view)
+        for field in result:
+            field['name'] = 'filter[{}]'.format(field['name'])
+        return result
diff --git a/rest_framework_json_api/management/__init__.py b/rest_framework_json_api/management/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/rest_framework_json_api/management/commands/__init__.py b/rest_framework_json_api/management/commands/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/rest_framework_json_api/management/commands/generateschema.py b/rest_framework_json_api/management/commands/generateschema.py
new file mode 100644
index 00000000..3c97b0d4
--- /dev/null
+++ b/rest_framework_json_api/management/commands/generateschema.py
@@ -0,0 +1,10 @@
+from rest_framework.management.commands.generateschema import Command as DRFCommand
+
+from rest_framework_json_api.schemas.openapi import SchemaGenerator
+
+
+class Command(DRFCommand):
+    help = "Generates jsonapi.org schema for project."
+
+    def get_generator_class(self):
+        return SchemaGenerator
diff --git a/rest_framework_json_api/schemas/__init__.py b/rest_framework_json_api/schemas/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/rest_framework_json_api/schemas/openapi.py b/rest_framework_json_api/schemas/openapi.py
new file mode 100644
index 00000000..68aa89e0
--- /dev/null
+++ b/rest_framework_json_api/schemas/openapi.py
@@ -0,0 +1,924 @@
+import warnings
+from urllib.parse import urljoin
+
+from django.conf import settings
+from django.db.models.fields import related_descriptors as rd
+from django.utils.module_loading import import_string as import_class_from_dotted_path
+from rest_framework import exceptions
+from rest_framework.authentication import BasicAuthentication, SessionAuthentication
+from rest_framework.relations import ManyRelatedField
+from rest_framework.schemas import openapi as drf_openapi
+from rest_framework.schemas.utils import is_list_view
+
+from rest_framework_json_api import serializers
+from rest_framework_json_api.views import RelationshipView
+
+try:
+    from oauth2_provider.contrib.rest_framework.authentication import OAuth2Authentication
+    from oauth2_provider.contrib.rest_framework.permissions import TokenMatchesOASRequirements
+except ImportError:
+    OAuth2Authentication = None
+    TokenMatchesOASRequirements = None
+
+#: static OAS 3.0 component definitions that are referenced by AutoSchema.
+JSONAPI_COMPONENTS = {
+    'schemas': {
+        'jsonapi': {
+            'type': 'object',
+            'description': "The server's implementation",
+            'properties': {
+                'version': {'type': 'string'},
+                'meta': {'$ref': '#/components/schemas/meta'}
+            },
+            'additionalProperties': False
+        },
+        'resource': {
+            'type': 'object',
+            'required': ['type', 'id'],
+            'additionalProperties': False,
+            'properties': {
+                'type': {
+                    '$ref': '#/components/schemas/type'
+                },
+                'id': {
+                    '$ref': '#/components/schemas/id'
+                },
+                'attributes': {
+                    'type': 'object',
+                    # ...
+                },
+                'relationships': {
+                    'type': 'object',
+                    # ...
+                },
+                'links': {
+                    '$ref': '#/components/schemas/links'
+                },
+                'meta': {'$ref': '#/components/schemas/meta'},
+            }
+        },
+        'link': {
+            'oneOf': [
+                {
+                    'description': "a string containing the link's URL",
+                    'type': 'string',
+                    'format': 'uri-reference'
+                },
+                {
+                    'type': 'object',
+                    'required': ['href'],
+                    'properties': {
+                        'href': {
+                            'description': "a string containing the link's URL",
+                            'type': 'string',
+                            'format': 'uri-reference'
+                        },
+                        'meta': {'$ref': '#/components/schemas/meta'}
+                    }
+                }
+            ]
+        },
+        'links': {
+            'type': 'object',
+            'additionalProperties': {'$ref': '#/components/schemas/link'}
+        },
+        'reltoone': {
+            'description': "a singular 'to-one' relationship",
+            'type': 'object',
+            'properties': {
+                'links': {'$ref': '#/components/schemas/relationshipLinks'},
+                'data': {'$ref': '#/components/schemas/relationshipToOne'},
+                'meta': {'$ref': '#/components/schemas/meta'}
+            }
+        },
+        'relationshipToOne': {
+            'description': "reference to other resource in a to-one relationship",
+            'anyOf': [
+                {'$ref': '#/components/schemas/nulltype'},
+                {'$ref': '#/components/schemas/linkage'}
+            ],
+        },
+        'reltomany': {
+            'description': "a multiple 'to-many' relationship",
+            'type': 'object',
+            'properties': {
+                'links': {'$ref': '#/components/schemas/relationshipLinks'},
+                'data': {'$ref': '#/components/schemas/relationshipToMany'},
+                'meta': {'$ref': '#/components/schemas/meta'}
+            }
+        },
+        'relationshipLinks': {
+            'description': 'optional references to other resource objects',
+            'type': 'object',
+            'additionalProperties': True,
+            'properties': {
+                'self': {'$ref': '#/components/schemas/link'},
+                'related': {'$ref': '#/components/schemas/link'}
+            }
+        },
+        'relationshipToMany': {
+            'description': "An array of objects each containing the "
+                           "'type' and 'id' for to-many relationships",
+            'type': 'array',
+            'items': {'$ref': '#/components/schemas/linkage'},
+            'uniqueItems': True
+        },
+        'linkage': {
+            'type': 'object',
+            'description': "the 'type' and 'id'",
+            'required': ['type', 'id'],
+            'properties': {
+                'type': {'$ref': '#/components/schemas/type'},
+                'id': {'$ref': '#/components/schemas/id'},
+                'meta': {'$ref': '#/components/schemas/meta'}
+            }
+        },
+        'pagination': {
+            'type': 'object',
+            'properties': {
+                'first': {'$ref': '#/components/schemas/pageref'},
+                'last': {'$ref': '#/components/schemas/pageref'},
+                'prev': {'$ref': '#/components/schemas/pageref'},
+                'next': {'$ref': '#/components/schemas/pageref'},
+            }
+        },
+        'pageref': {
+            'oneOf': [
+                {'type': 'string', 'format': 'uri-reference'},
+                {'$ref': '#/components/schemas/nulltype'}
+            ]
+        },
+        'failure': {
+            'type': 'object',
+            'required': ['errors'],
+            'properties': {
+                'errors': {'$ref': '#/components/schemas/errors'},
+                'meta': {'$ref': '#/components/schemas/meta'},
+                'jsonapi': {'$ref': '#/components/schemas/jsonapi'},
+                'links': {'$ref': '#/components/schemas/links'}
+            }
+        },
+        'errors': {
+            'type': 'array',
+            'items': {'$ref': '#/components/schemas/error'},
+            'uniqueItems': True
+        },
+        'error': {
+            'type': 'object',
+            'additionalProperties': False,
+            'properties': {
+                'id': {'type': 'string'},
+                'status': {'type': 'string'},
+                'links': {'$ref': '#/components/schemas/links'},
+                'code': {'type': 'string'},
+                'title': {'type': 'string'},
+                'detail': {'type': 'string'},
+                'source': {
+                    'type': 'object',
+                    'properties': {
+                        'pointer': {
+                            'type': 'string',
+                            'description':
+                                "A [JSON Pointer](https://tools.ietf.org/html/rfc6901) "
+                                "to the associated entity in the request document "
+                                "[e.g. `/data` for a primary data object, or "
+                                "`/data/attributes/title` for a specific attribute."
+                        },
+                        'parameter': {
+                            'type': 'string',
+                            'description':
+                                "A string indicating which query parameter "
+                                "caused the error."
+                        },
+                        'meta': {'$ref': '#/components/schemas/meta'}
+                    }
+                }
+            }
+        },
+        'onlymeta': {
+            'additionalProperties': False,
+            'properties': {
+                'meta': {'$ref': '#/components/schemas/meta'}
+            }
+        },
+        'meta': {
+            'type': 'object',
+            'additionalProperties': True
+        },
+        'datum': {
+            'description': 'singular item',
+            'properties': {
+                'data': {'$ref': '#/components/schemas/resource'}
+            }
+        },
+        'nulltype': {
+            'type': 'object',
+            'nullable': True,
+            'default': None
+        },
+        'type': {
+            'type': 'string',
+            'description':
+                'The [type]'
+                '(https://jsonapi.org/format/#document-resource-object-identification) '
+                'member is used to describe resource objects that share common attributes '
+                'and relationships.'
+        },
+        'id': {
+            'type': 'string',
+            'description':
+                "Each resource object’s type and id pair MUST "
+                "[identify]"
+                "(https://jsonapi.org/format/#document-resource-object-identification) "
+                "a single, unique resource."
+        },
+    },
+    'parameters': {
+        'include': {
+            'name': 'include',
+            'in': 'query',
+            'description': '[list of included related resources]'
+                           '(https://jsonapi.org/format/#fetching-includes)',
+            'required': False,
+            'style': 'form',
+            'schema': {
+                'type': 'string'
+            }
+        },
+        # TODO: deepObject not well defined/supported:
+        #       https://github.com/OAI/OpenAPI-Specification/issues/1706
+        'fields': {
+            'name': 'fields',
+            'in': 'query',
+            'description': '[sparse fieldsets]'
+                           '(https://jsonapi.org/format/#fetching-sparse-fieldsets)',
+            'required': False,
+            'style': 'deepObject',
+            'schema': {
+                'type': 'object',
+            },
+            'explode': True
+        },
+        'sort': {
+            'name': 'sort',
+            'in': 'query',
+            'description': '[list of fields to sort by]'
+                           '(https://jsonapi.org/format/#fetching-sorting)',
+            'required': False,
+            'style': 'form',
+            'schema': {
+                'type': 'string'
+            }
+        },
+    },
+}
+
+
+class SchemaGenerator(drf_openapi.SchemaGenerator):
+    """
+    Extend DRF's SchemaGenerator to implement jsonapi-flavored generateschema command
+    """
+    def __init__(self, *args, **kwargs):
+        self.openapi_schema = {}
+        return super().__init__(*args, **kwargs)
+
+    def get_schema(self, request=None, public=False):
+        """
+        Generate a JSONAPI OpenAPI schema.
+        """
+        self._initialise_endpoints()
+
+        paths = self.get_paths(None if public else request)
+        if not paths:
+            return None
+        schema = {
+            'openapi': '3.0.2',
+            'info': self.get_info(),
+            'paths': paths,
+            'components': JSONAPI_COMPONENTS,
+        }
+
+        return {**schema, **self.openapi_schema}
+
+    def get_paths(self, request=None):
+        """
+        **Replacement** for rest_framework.schemas.openapi.SchemaGenerator.get_paths():
+        - expand the paths for RelationshipViews and retrieve_related actions:
+          {related_field} gets replaced by the related field names.
+        - Merges in any openapi_schema initializer that the view has.
+        """
+        result = {}
+
+        paths, view_endpoints = self._get_paths_and_endpoints(request)
+
+        # Only generate the path prefix for paths that will be included
+        if not paths:
+            return None
+
+        #: `expanded_endpoints` is like view_endpoints with one extra field tacked on:
+        #: - 'action' copy of current view.action (list/fetch) as this gets reset for each request.
+        # TODO: define an endpoint_inspector_cls that extends EndpointEnumerator
+        #       instead of doing it here.
+        expanded_endpoints = []
+        for path, method, view in view_endpoints:
+            if isinstance(view, RelationshipView):
+                expanded_endpoints += self._expand_relationships(path, method, view)
+            elif view.action == 'retrieve_related':
+                expanded_endpoints += self._expand_related(path, method, view, view_endpoints)
+            else:
+                expanded_endpoints.append((path, method, view, view.action))
+
+        for path, method, view, action in expanded_endpoints:
+            if not self.has_view_permissions(path, method, view):
+                continue
+            # kludge to preserve view.action as it changes "globally" for the same ViewSet
+            # whether it is used for a collection, item or related serializer. _expand_related
+            # sets it based on whether the related field is a toMany collection or toOne item.
+            current_action = None
+            if hasattr(view, 'action'):
+                current_action = view.action
+                view.action = action
+            operation = view.schema.get_operation(path, method, action)
+            if hasattr(view, 'action'):
+                view.action = current_action
+            operation['description'] = operation['operationId']  # TODO: kludge
+            if 'responses' in operation and '200' in operation['responses']:
+                operation['responses']['200']['description'] = operation['operationId']  # TODO:!
+            # Normalise path for any provided mount url.
+            if path.startswith('/'):
+                path = path[1:]
+            path = urljoin(self.url or '/', path)
+
+            result.setdefault(path, {})
+            result[path][method.lower()] = operation
+            if hasattr(view.schema, 'openapi_schema'):
+                # TODO: shallow or deep merge?
+                self.openapi_schema = {**self.openapi_schema, **view.schema.openapi_schema}
+
+        return result
+
+    def _expand_relationships(self, path, method, view):
+        """
+        Expand path containing .../{id}/relationships/{related_field} into list of related fields.
+        :return:list[tuple(path, method, view, action)]
+        """
+        queryset = view.get_queryset()
+        if not queryset or not queryset.model:
+            return [(path, method, view, getattr(view, 'action', '')), ]
+        result = []
+        m = queryset.model
+        for field in [f for f in dir(m) if not f.startswith('_')]:
+            attr = getattr(m, field)
+            if isinstance(attr, (rd.ReverseManyToOneDescriptor, rd.ForwardOneToOneDescriptor)):
+                action = 'rels' if isinstance(attr, rd.ReverseManyToOneDescriptor) else 'rel'
+                result.append((path.replace('{related_field}', field), method, view, action))
+
+        return result
+
+    def _expand_related(self, path, method, view, view_endpoints):
+        """
+        Expand path containing .../{id}/{related_field} into list of related fields
+        and **their** views, making sure toOne relationship's views are a 'fetch' and toMany
+        relationship's are a 'list'.
+        :param path
+        :param method
+        :param view
+        :param view_endpoints
+        :return:list[tuple(path, method, view, action)]
+        """
+        result = []
+        serializer = view.get_serializer()
+        if hasattr(serializer, 'related_serializers'):
+            related_fields = [fs for fs in serializer.related_serializers.items()]
+        elif hasattr(serializer, 'included_serializers'):
+            related_fields = [fs for fs in serializer.included_serializers.items()]
+        else:
+            related_fields = []
+        for field, related_serializer in related_fields:
+            related_view = self._find_related_view(view_endpoints, related_serializer, view)
+            if related_view:
+                action = self._field_is_one_or_many(field, view)
+                result.append(
+                    (path.replace('{related_field}', field), method, related_view, action)
+                )
+
+        return result
+
+    def _find_related_view(self, view_endpoints, related_serializer, parent_view):
+        """
+        For a given related_serializer, try to find it's "parent" view instance in view_endpoints.
+        :param view_endpoints: list of all view endpoints
+        :param related_serializer: the related serializer for a given related field
+        :param parent_view: the parent view (used to find toMany vs. toOne).
+        :return:view
+        """
+        for path, method, view in view_endpoints:
+            view_serializer = view.get_serializer()
+            if not isinstance(related_serializer, type):
+                related_serializer_class = import_class_from_dotted_path(related_serializer)
+            else:
+                related_serializer_class = related_serializer
+            if isinstance(view_serializer, related_serializer_class):
+                return view
+
+        return None
+
+    def _field_is_one_or_many(self, field, view):
+        serializer = view.get_serializer()
+        if isinstance(serializer.fields[field], ManyRelatedField):
+            return 'list'
+        else:
+            return 'fetch'
+
+
+class AutoSchema(drf_openapi.AutoSchema):
+    """
+    Extend DRF's openapi.AutoSchema for JSONAPI serialization.
+    """
+    content_types = ['application/vnd.api+json']
+
+    def __init__(self, openapi_schema={}):
+        """
+        Initialize the JSONAPI OAS schema generator
+        :param openapi_schema: dict: OAS 3.0 document with initial values.
+        """
+        super().__init__()
+        #: allow initialization of OAS schema doc
+        self.openapi_schema = openapi_schema
+        # static JSONAPI fields that get $ref'd to in the view mappings
+        jsonapi_ref = {
+            'components': JSONAPI_COMPONENTS
+        }
+        # merge in our reference data on top of anything provided by the init.
+        # TODO: shallow or deep merge?
+        self.openapi_schema = {**self.openapi_schema, **jsonapi_ref}
+
+    def get_operation(self, path, method, action=None):
+        """ basically a copy of AutoSchema.get_operation """
+        operation = {}
+        operation['operationId'] = self._get_operation_id(path, method)
+        operation['security'] = self._get_security(path, method)
+
+        parameters = []
+        parameters += self._get_path_parameters(path, method)
+        # pagination, filters only apply to GET/HEAD of collections and items
+        if method in ['GET', 'HEAD']:
+            parameters += self._get_include_parameters(path, method)
+            parameters += self._get_fields_parameters(path, method)
+            parameters += self._get_sort_parameters(path, method)
+            parameters += self._get_pagination_parameters(path, method)
+            parameters += self._get_filter_parameters(path, method)
+        operation['parameters'] = parameters
+
+        # get request and response code schemas
+        if method == 'GET':
+            if is_list_view(path, method, self.view):
+                self._get_collection(operation)
+            else:
+                self._get_item(operation)
+        elif method == 'POST':
+            self._post_item(operation, path, action)
+        elif method == 'PATCH':
+            self._patch_item(operation, path, action)
+        elif method == 'DELETE':
+            # should only allow deleting a resource, not a collection
+            # TODO: delete of a relationship is different.
+            self._delete_item(operation, path, action)
+        return operation
+
+    def _get_operation_id(self, path, method):
+        """ create a unique operationId """
+        # The DRF version creates non-unique operationIDs, especially when the same view is used
+        # for different paths. Just make a simple concatenation of (mapped) method name and path.
+        method_name = getattr(self.view, 'action', method.lower())
+        if is_list_view(path, method, self.view):
+            action = 'List'
+        elif method_name not in self.method_mapping:
+            action = method_name
+        else:
+            action = self.method_mapping[method.lower()]
+        return action + path
+
+    def _get_security(self, path, method):
+        # TODO: flesh this out and move to DRF openapi.
+        content = []
+        for auth_class in self.view.authentication_classes:
+            if issubclass(auth_class, BasicAuthentication):
+                content.append({'basicAuth': []})
+                self.openapi_schema['components']['securitySchemes'] = {
+                    'basicAuth': {'type': 'http', 'scheme': 'basic'}
+                }
+                continue
+            if issubclass(auth_class, SessionAuthentication):
+                continue                # TODO: can this be represented?
+            # TODO: how to do this? needs permission_classes, etc. and is not super-consistent.
+            if OAuth2Authentication and issubclass(auth_class, OAuth2Authentication):
+                content += self._get_oauth_security(path, method)
+                continue
+        return content
+
+    def _get_oauth_security(self, path, method):
+        """
+        Creates `#components/securitySchemes/oauth` and returns `.../security/oauth`
+        when using Django OAuth Toolkit.
+        """
+        # TODO: make DOT an optional import
+        # openIdConnect type not currently supported by Swagger-UI
+        # 'openIdConnectUrl': settings.OAUTH2_SERVER + '/.well-known/openid-configuration'
+        if not hasattr(settings, 'OAUTH2_CONFIG'):
+            return []
+        self.openapi_schema['components']['securitySchemes']['oauth'] = {
+            'type': 'oauth2',
+            'description': 'oauth2.0 service',
+        }
+        flows = {}
+        if 'authorization_code' in settings.OAUTH2_CONFIG['grant_types_supported']:
+            flows['authorizationCode'] = {
+                'authorizationUrl': settings.OAUTH2_CONFIG['authorization_endpoint'],
+                'tokenUrl': settings.OAUTH2_CONFIG['token_endpoint'],
+                'refreshUrl': settings.OAUTH2_CONFIG['token_endpoint'],
+                'scopes': {s: s for s in settings.OAUTH2_CONFIG['scopes_supported']}
+            }
+        if 'implicit' in settings.OAUTH2_CONFIG['grant_types_supported']:
+            flows['implicit'] = {
+                'authorizationUrl': settings.OAUTH2_CONFIG['authorization_endpoint'],
+                'scopes': {s: s for s in settings.OAUTH2_CONFIG['scopes_supported']}
+            }
+        if 'client_credentials' in settings.OAUTH2_CONFIG['grant_types_supported']:
+            flows['clientCredentials'] = {
+                'tokenUrl': settings.OAUTH2_CONFIG['token_endpoint'],
+                'refreshUrl': settings.OAUTH2_CONFIG['token_endpoint'],
+                'scopes': {s: s for s in settings.OAUTH2_CONFIG['scopes_supported']}
+            }
+        if 'password' in settings.OAUTH2_CONFIG['grant_types_supported']:
+            flows['password'] = {
+                'tokenUrl': settings.OAUTH2_CONFIG['token_endpoint'],
+                'refreshUrl': settings.OAUTH2_CONFIG['token_endpoint'],
+                'scopes': {s: s for s in settings.OAUTH2_CONFIG['scopes_supported']}
+            }
+        self.openapi_schema['components']['securitySchemes']['oauth']['flows'] = flows
+        # TODO: add JWT and SAML2 bearer
+        content = []
+        for perm_class in self.view.permission_classes:
+            if (TokenMatchesOASRequirements and
+                    issubclass(perm_class.perms_or_conds[0], TokenMatchesOASRequirements)):
+                alt_scopes = self.view.required_alternate_scopes
+                if method not in alt_scopes:
+                    continue
+                for scopes in alt_scopes[method]:
+                    content.append({'oauth': scopes})
+        return content
+
+    def _get_include_parameters(self, path, method):
+        """
+        includes parameter: https://jsonapi.org/format/#fetching-includes
+        """
+        return [{'$ref': '#/components/parameters/include'}]
+
+    def _get_fields_parameters(self, path, method):
+        """
+        sparse fieldsets https://jsonapi.org/format/#fetching-sparse-fieldsets
+        """
+        return [{'$ref': '#/components/parameters/fields'}]
+
+    def _get_sort_parameters(self, path, method):
+        """
+        sort parameter: https://jsonapi.org/format/#fetching-sorting
+        """
+        return [{'$ref': '#/components/parameters/sort'}]
+
+    def _get_collection(self, operation):
+        """
+        jsonapi-structured 200 response for GET of a collection
+        """
+        operation['responses'] = {
+            '200': self._get_toplevel_200_response(operation, collection=True)
+        }
+        self._add_get_4xx_responses(operation)
+
+    def _get_item(self, operation):
+        """ jsonapi-structured response for GET of an item """
+        operation['responses'] = {
+            '200': self._get_toplevel_200_response(operation, collection=False)
+        }
+        self._add_get_4xx_responses(operation)
+
+    def _get_toplevel_200_response(self, operation, collection=True):
+        """ top-level JSONAPI GET 200 response """
+        if collection:
+            data = {'type': 'array', 'items': self._get_item_schema(operation)}
+        else:
+            data = self._get_item_schema(operation)
+
+        return {
+            'description': operation['operationId'],
+            'content': {
+                'application/vnd.api+json': {
+                    'schema': {
+                        'type': 'object',
+                        'required': ['data'],
+                        'properties': {
+                            'data': data,
+                            'included': {
+                                'type': 'array',
+                                'uniqueItems': True,
+                                'items': {
+                                    '$ref': '#/components/schemas/resource'
+                                }
+                            },
+                            'links': {
+                                'description': 'Link members related to primary data',
+                                'allOf': [
+                                    {'$ref': '#/components/schemas/links'},
+                                    {'$ref': '#/components/schemas/pagination'}
+                                ]
+                            },
+                            'jsonapi': {
+                                '$ref': '#/components/schemas/jsonapi'
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+    def _get_item_schema(self, operation):
+        """
+        get the schema for item
+        """
+        content = {}
+        view = self.view
+        if hasattr(view, 'get_serializer'):
+            try:
+                serializer = view.get_serializer()
+            except exceptions.APIException:
+                serializer = None
+                warnings.warn('{}.get_serializer() raised an exception during '
+                              'schema generation. Serializer fields will not be '
+                              'generated.'.format(view.__class__.__name__))
+
+            if isinstance(serializer, serializers.BaseSerializer):
+                content = self._map_serializer(serializer)
+                # No write_only fields for response.
+                for name, schema in content['properties'].copy().items():
+                    if 'writeOnly' in schema:
+                        del content['properties'][name]
+                        content['required'] = [f for f in content['required'] if f != name]
+                content['properties']['type'] = {'$ref': '#/components/schemas/type'}
+                content['properties']['id'] = {'$ref': '#/components/schemas/id'}
+
+        return content
+
+    def _post_item(self, operation, path, action):
+        """ jsonapi-strucutred response for POST of an item """
+        operation['requestBody'] = self._get_request_body(path, 'POST', action)
+        operation['responses'] = {
+            '201': self._get_toplevel_200_response(operation, collection=False)
+        }
+        operation['responses']['201']['description'] = \
+            '[Created](https://jsonapi.org/format/#crud-creating-responses-201). '\
+            'Assigned `id` and/or any other changes are in this response.'
+        self._add_async_response(operation)
+        operation['responses']['204'] = {
+            'description': '[Created](https://jsonapi.org/format/#crud-creating-responses-204) '
+            'with the supplied `id`. No other changes from what was POSTed.'
+        }
+        self._add_post_4xx_responses(operation)
+
+    def _patch_item(self, operation, path, action):
+        """ jsomapi-strucutred response for PATCH of an item """
+        operation['requestBody'] = self._get_request_body(path, 'PATCH', action)
+        operation['responses'] = {
+            '200': self._get_toplevel_200_response(operation, collection=False)
+        }
+        self._add_patch_4xx_responses(operation)
+
+    def _delete_item(self, operation, path, action):
+        """ jsonapi-structured response for DELETE of an item or relationship? """
+        # Only DELETE of relationships has a requestBody
+        if action in ['rels', 'rel']:
+            operation['requestBody'] = self._get_request_body(path, 'DELETE', action)
+
+        self._add_delete_responses(operation)
+
+    def _get_request_body(self, path, method, action):
+        """ jsonapi-flavored request_body """
+        # TODO: if a RelationshipView, check for toMany (data array) vs. toOne.
+        content = {}
+        view = self.view
+
+        if not hasattr(view, 'get_serializer'):
+            return {}
+
+        try:
+            serializer = view.get_serializer()
+        except exceptions.APIException:
+            serializer = None
+            warnings.warn('{}.get_serializer() raised an exception during '
+                          'schema generation. Serializer fields will not be '
+                          'generated for {} {}.'
+                          .format(view.__class__.__name__, method, path))
+
+        # ResourceIdentifierObjectSerializer
+        if not isinstance(serializer, (serializers.BaseSerializer, )):
+            return {}
+
+        content = self._map_serializer(serializer)
+
+        # 'type' and 'id' are both required for:
+        # - all relationship operations
+        # - regular PATCH or DELETE
+        # Only 'type' is required for POST: system may assign the 'id'.
+        if action in ['rels', 'rel']:
+            content['required'] = ['type', 'id']
+        elif method in ['PATCH', 'DELETE']:
+            content['required'] = ['type', 'id']
+        elif method == 'POST':
+            content['required'] = ['type']
+
+        if 'attributes' in content['properties']:
+            # No required attributes for PATCH
+            if method in ['PATCH', 'PUT'] and 'required' in content['properties']['attributes']:
+                del content['properties']['attributes']['required']
+            # No read_only fields for request.
+            for name, schema in content['properties']['attributes']['properties'].copy().items():
+                if 'readOnly' in schema:
+                    del content['properties']['attributes']['properties'][name]
+        # relationships special case: plural request body (data is array of items)
+        if action == 'rels':
+            return {
+                'content': {
+                    ct: {
+                        'schema': {
+                            'required': ['data'],
+                            'properties': {
+                                'data': {
+                                    'type': 'array',
+                                    'items': content
+                                }
+                            }
+                        }
+                    }
+                    for ct in self.content_types
+                }
+            }
+        else:
+            return {
+                'content': {
+                    ct: {
+                        'schema': {
+                            'required': ['data'],
+                            'properties': {
+                                'data': content
+                            }
+                        }
+                    }
+                    for ct in self.content_types
+                }
+            }
+
+    def _map_serializer(self, serializer):
+        """
+        Custom map_serializer that serializes the schema using the jsonapi spec.
+        Non-attributes like related and identity fields, are move to 'relationships' and 'links'.
+        """
+        # TODO: remove attributes, etc. for relationshipView??
+        required = []
+        attributes = {}
+        relationships = {}
+
+        for field in serializer.fields.values():
+            if isinstance(field, serializers.HyperlinkedIdentityField):
+                # the 'url' is not an attribute but rather a self.link, so don't map it here.
+                continue
+            if isinstance(field, serializers.HiddenField):
+                continue
+            if isinstance(field, serializers.RelatedField):
+                relationships[field.field_name] = {'$ref': '#/components/schemas/reltoone'}
+                continue
+            if isinstance(field, serializers.ManyRelatedField):
+                relationships[field.field_name] = {'$ref': '#/components/schemas/reltomany'}
+                continue
+
+            if field.required:
+                required.append(field.field_name)
+
+            schema = self._map_field(field)
+            if field.help_text:
+                schema['description'] = field.help_text
+            self._map_field_validators(field.validators, schema)
+            if field.read_only:
+                schema['readOnly'] = True
+            if field.write_only:
+                schema['writeOnly'] = True
+            if field.allow_null:
+                schema['nullable'] = True
+
+            attributes[field.field_name] = schema
+        result = {
+            'type': 'object',
+            'required': ['type', 'id'],
+            'additionalProperties': False,
+            'properties': {
+                'type': {'$ref': '#/components/schemas/type'},
+                'id': {'$ref': '#/components/schemas/id'},
+                'links': {
+                    'type': 'object',
+                    'properties': {
+                        'self': {'$ref': '#/components/schemas/link'}
+                    }
+                }
+            }
+        }
+        if attributes:
+            result['properties']['attributes'] = {
+                'type': 'object',
+                'properties': attributes
+            }
+        if relationships:
+            result['properties']['relationships'] = {
+                'type': 'object',
+                'properties': relationships
+            }
+        if required:
+            result['properties']['attributes']['required'] = required
+        return result
+
+    def _add_async_response(self, operation):
+        operation['responses']['202'] = {
+            'description': 'Accepted for [asynchronous processing]'
+                           '(https://jsonapi.org/recommendations/#asynchronous-processing)',
+            'content': {
+                'application/vnd.api+json': {
+                    'schema': {'$ref': '#/components/schemas/datum'}
+                }
+            }
+        }
+
+    def _failure_response(self, reason):
+        return {
+            'description': reason,
+            'content': {
+                'application/vnd.api+json': {
+                    'schema': {'$ref': '#/components/schemas/failure'}
+                }
+            }
+        }
+
+    def _generic_failure_responses(self, operation):
+        for code, reason in [('401', 'not authorized'), ]:
+            operation['responses'][code] = self._failure_response(reason)
+
+    def _add_get_4xx_responses(self, operation):
+        """ Add generic responses for get """
+        self._generic_failure_responses(operation)
+        for code, reason in [('404', 'not found')]:
+            operation['responses'][code] = self._failure_response(reason)
+
+    def _add_post_4xx_responses(self, operation):
+        """ Add error responses for post """
+        self._generic_failure_responses(operation)
+        for code, reason in [
+            ('403', '[Forbidden](https://jsonapi.org/format/#crud-creating-responses-403)'),
+            ('404', '[Related resource does not exist]'
+                    '(https://jsonapi.org/format/#crud-creating-responses-404)'),
+            ('409', '[Conflict](https://jsonapi.org/format/#crud-creating-responses-409)'),
+        ]:
+            operation['responses'][code] = self._failure_response(reason)
+
+    def _add_patch_4xx_responses(self, operation):
+        """ Add error responses for patch """
+        self._generic_failure_responses(operation)
+        for code, reason in [
+            ('403', '[Forbidden](https://jsonapi.org/format/#crud-updating-responses-403)'),
+            ('404', '[Related resource does not exist]'
+                    '(https://jsonapi.org/format/#crud-updating-responses-404)'),
+            ('409', '[Conflict]([Conflict]'
+                    '(https://jsonapi.org/format/#crud-updating-responses-409)'),
+        ]:
+            operation['responses'][code] = self._failure_response(reason)
+
+    def _add_delete_responses(self, operation):
+        """ Add generic responses for delete """
+        # the 2xx statuses:
+        operation['responses'] = {
+            '200': {
+                'description': '[OK](https://jsonapi.org/format/#crud-deleting-responses-200)',
+                'content': {
+                    'application/vnd.api+json': {
+                        'schema': {'$ref': '#/components/schemas/onlymeta'}
+                    }
+                }
+            }
+        }
+        self._add_async_response(operation)
+        operation['responses']['204'] = {
+            'description': '[no content](https://jsonapi.org/format/#crud-deleting-responses-204)',
+        }
+        # the 4xx errors:
+        self._generic_failure_responses(operation)
+        for code, reason in [
+            ('404', '[Resource does not exist]'
+                    '(https://jsonapi.org/format/#crud-deleting-responses-404)'),
+        ]:
+            operation['responses'][code] = self._failure_response(reason)
diff --git a/tox.ini b/tox.ini
index c67aff84..760ee1e8 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,7 +1,7 @@
 [tox]
 envlist =
-    py{35,36}-django111-drf{39,310,master},
-    py{35,36,37}-django{21,22}-drf{39,310,master},
+    py{35,36}-django111-drf{39,310,master}-oauth11,
+    py{35,36,37}-django{21,22}-drf{39,310,master}-oauth12,
 
 [testenv]
 deps =
@@ -11,6 +11,9 @@ deps =
     drf39: djangorestframework>=3.9.0,<3.10
     drf310: djangorestframework>=3.10.2,<3.11
     drfmaster: https://github.com/encode/django-rest-framework/archive/master.zip
+    oauth11: django-oauth-toolkit>=1.1.2,<1.2
+    oauth12: django-oauth-toolkit>=1.2.0
+    coreapi>=2.3.1
 
 setenv =
     PYTHONPATH = {toxinidir}