diff --git a/README.md b/README.md
index 3366a1a8..618e6dd4 100644
--- a/README.md
+++ b/README.md
@@ -35,7 +35,7 @@ Assuming you've got an ```$author``` of type ```\Author``` you can encode it to
 ```php
 $encoder = Encoder::instance([
     '\Author' => '\AuthorSchema',
-], new JsonEncodeOptions(JSON_PRETTY_PRINT));
+], new EncoderOptions(JSON_PRETTY_PRINT, 'http://example.com/api/v1'));
 
 echo $encoder->encode($author) . PHP_EOL;
 ```
@@ -52,7 +52,7 @@ will output
             "last_name": "Dow"
         },
         "links": {
-            "self": "http:\/\/example.com\/people\/123"
+            "self": "http://example.com/api/v1/people/123"
         }
     }
 }
@@ -64,7 +64,7 @@ The ```AuthorSchema``` provides information about resource's attributes and migh
 class AuthorSchema extends SchemaProvider
 {
     protected $resourceType = 'people';
-    protected $baseSelfUrl  = 'http://example.com/people/';
+    protected $selfSubUrl   = '/people/';
 
     public function getId($author)
     {
@@ -83,6 +83,10 @@ class AuthorSchema extends SchemaProvider
 }
 ```
 
+The first ```EncoderOptions``` parameter ```JSON_PRETTY_PRINT``` is a PHP predefined [JSON constant](http://php.net/manual/en/json.constants.php).
+
+The second ```EncoderOptions``` parameter ```http://example.com/api/v1``` is a URL prefix that will be applied to all encoded links unless they have ```$treatAsHref``` flag set to ```true```.
+
 **For more advanced usage please check out the [Wiki](https://github.com/neomerx/json-api/wiki)**.
 
 ## Questions?
diff --git a/sample/composer.json b/sample/composer.json
index c3e73c99..005e7252 100644
--- a/sample/composer.json
+++ b/sample/composer.json
@@ -16,6 +16,6 @@
     },
     "minimum-stability": "dev",
     "require": {
-        "neomerx/json-api": "0.4.2"
+        "neomerx/json-api": "~0.5.0"
     }
 }
diff --git a/sample/sample.php b/sample/sample.php
index 4361bb0c..b3ac7346 100644
--- a/sample/sample.php
+++ b/sample/sample.php
@@ -18,7 +18,7 @@
 
 use \Neomerx\JsonApi\Schema\Link;
 use \Neomerx\JsonApi\Encoder\Encoder;
-use \Neomerx\JsonApi\Encoder\JsonEncodeOptions;
+use \Neomerx\JsonApi\Encoder\EncoderOptions;
 use \Neomerx\JsonApi\Parameters\EncodingParameters;
 
 require './vendor/autoload.php';
@@ -39,7 +39,7 @@ private function showBasicExample()
 
         $encoder = Encoder::instance([
             Author::class  => AuthorSchema::class,
-        ], new JsonEncodeOptions(JSON_PRETTY_PRINT));
+        ], new EncoderOptions(JSON_PRETTY_PRINT, 'http://example.com/api/v1'));
 
         echo $encoder->encode($author) . PHP_EOL;
     }
@@ -64,7 +64,7 @@ private function showIncludedObjectsExample()
             Comment::class => CommentSchema::class,
             Post::class    => PostSchema::class,
             Site::class    => SiteSchema::class
-        ], new JsonEncodeOptions(JSON_PRETTY_PRINT));
+        ], new EncoderOptions(JSON_PRETTY_PRINT, 'http://example.com'));
 
         echo $encoder->encode($site) . PHP_EOL;
     }
@@ -97,7 +97,7 @@ private function showSparseAndFieldSetsExample()
             Comment::class => CommentSchema::class,
             Post::class    => PostSchema::class,
             Site::class    => SiteSchema::class
-        ], new JsonEncodeOptions(JSON_PRETTY_PRINT));
+        ], new EncoderOptions(JSON_PRETTY_PRINT));
 
         echo $encoder->encode($site, null, null, $options) . PHP_EOL;
     }
@@ -119,10 +119,10 @@ private function showTopLevelMetaAndLinksExample()
             ]
         ];
         $links  = [
-            Link::FIRST => new Link('http://example.com/people?first'),
-            Link::LAST  => new Link('http://example.com/people?last'),
-            Link::PREV  => new Link('http://example.com/people?prev'),
-            Link::NEXT  => new Link('http://example.com/people?next'),
+            Link::FIRST => new Link('http://example.com/people?first', null, true),
+            Link::LAST  => new Link('http://example.com/people?last', null, true),
+            Link::PREV  => new Link('http://example.com/people?prev', null, true),
+            Link::NEXT  => new Link('http://example.com/people?next', null, true),
         ];
 
         $encoder  = Encoder::instance([
@@ -130,7 +130,7 @@ private function showTopLevelMetaAndLinksExample()
             Comment::class => CommentSchema::class,
             Post::class    => PostSchema::class,
             Site::class    => SiteSchema::class
-        ], new JsonEncodeOptions(JSON_PRETTY_PRINT));
+        ], new EncoderOptions(JSON_PRETTY_PRINT, 'http://example.com'));
 
         echo $encoder->encode($author, $links, $meta) . PHP_EOL;
     }
@@ -166,7 +166,7 @@ private function runPerformanceTest($iterations)
 
             $encoder->encode(
                 $site,
-                [Link::SELF => new Link('http://example.com/sites/1?' . $rand)],
+                [Link::SELF => new Link('http://example.com/sites/1?' . $rand, null, true)],
                 ['some' => ['meta' => 'information' . $rand]],
                 $options
             );
diff --git a/sample/schemas/AuthorSchema.php b/sample/schemas/AuthorSchema.php
index 2ea54b0d..deb7bf41 100644
--- a/sample/schemas/AuthorSchema.php
+++ b/sample/schemas/AuthorSchema.php
@@ -24,7 +24,7 @@
 class AuthorSchema extends SchemaProvider
 {
     protected $resourceType = 'people';
-    protected $baseSelfUrl  = 'http://example.com/people/';
+    protected $selfSubUrl  = '/people/';
 
     public function getId($author)
     {
diff --git a/sample/schemas/CommentSchema.php b/sample/schemas/CommentSchema.php
index 646a39e5..8998101d 100644
--- a/sample/schemas/CommentSchema.php
+++ b/sample/schemas/CommentSchema.php
@@ -24,7 +24,7 @@
 class CommentSchema extends SchemaProvider
 {
     protected $resourceType = 'comments';
-    protected $baseSelfUrl  = 'http://example.com/comments/';
+    protected $selfSubUrl  = '/comments/';
 
     protected $isShowSelfInIncluded = true;
 
diff --git a/sample/schemas/PostSchema.php b/sample/schemas/PostSchema.php
index 43a3607c..d092732c 100644
--- a/sample/schemas/PostSchema.php
+++ b/sample/schemas/PostSchema.php
@@ -24,7 +24,7 @@
 class PostSchema extends SchemaProvider
 {
     protected $resourceType = 'posts';
-    protected $baseSelfUrl  = 'http://example.com/posts';
+    protected $selfSubUrl  = '/posts/';
 
     public function getId($post)
     {
diff --git a/sample/schemas/SiteSchema.php b/sample/schemas/SiteSchema.php
index 23a01458..5da1f757 100644
--- a/sample/schemas/SiteSchema.php
+++ b/sample/schemas/SiteSchema.php
@@ -24,7 +24,7 @@
 class SiteSchema extends SchemaProvider
 {
     protected $resourceType = 'sites';
-    protected $baseSelfUrl  = 'http://example.com/sites';
+    protected $selfSubUrl  = '/sites/';
 
     public function getId($site)
     {
diff --git a/src/Codec/CodecMatcher.php b/src/Codec/CodecMatcher.php
index e7cd3deb..10ced135 100644
--- a/src/Codec/CodecMatcher.php
+++ b/src/Codec/CodecMatcher.php
@@ -52,12 +52,12 @@ class CodecMatcher implements CodecMatcherInterface
     private $inputMediaTypes;
 
     /**
-     * @var EncoderInterface|null
+     * @var EncoderInterface|Closure|null
      */
     private $foundEncoder;
 
     /**
-     * @var DecoderInterface|null
+     * @var DecoderInterface|Closure|null
      */
     private $foundDecoder;
 
@@ -102,17 +102,43 @@ public function registerDecoder(MediaTypeInterface $mediaType, Closure $decoderC
      */
     public function getEncoder()
     {
+        if ($this->foundEncoder instanceof Closure) {
+            $closure = $this->foundEncoder;
+            $this->foundEncoder = $closure();
+        }
+
         return $this->foundEncoder;
     }
 
+    /**
+     * @inheritdoc
+     */
+    public function setEncoder($encoder)
+    {
+        $this->foundEncoder = $encoder;
+    }
+
     /**
      * @inheritdoc
      */
     public function getDecoder()
     {
+        if ($this->foundDecoder instanceof Closure) {
+            $closure = $this->foundDecoder;
+            $this->foundDecoder = $closure();
+        }
+
         return $this->foundDecoder;
     }
 
+    /**
+     * @inheritdoc
+     */
+    public function setDecoder($decoder)
+    {
+        $this->foundDecoder = $decoder;
+    }
+
     /**
      * @inheritdoc
      */
@@ -126,7 +152,7 @@ public function matchEncoder(AcceptHeaderInterface $acceptHeader)
                     if ($registeredType->matchesTo($headerMediaType) === true) {
                         $this->encoderHeaderMatchedType     = $headerMediaType;
                         $this->encoderRegisteredMatchedType = $registeredType;
-                        $this->foundEncoder                 = $closure();
+                        $this->foundEncoder                 = $closure;
 
                         return;
                     }
@@ -154,7 +180,7 @@ public function findDecoder(HeaderInterface $contentTypeHeader)
                 if ($registeredType->equalsTo($headerMediaType) === true) {
                     $this->decoderHeaderMatchedType     = $headerMediaType;
                     $this->decoderRegisteredMatchedType = $registeredType;
-                    $this->foundDecoder                 = $closure();
+                    $this->foundDecoder                 = $closure;
 
                     return;
                 }
diff --git a/src/Contracts/Codec/CodecMatcherInterface.php b/src/Contracts/Codec/CodecMatcherInterface.php
index 6cfd5d5b..59eca2ac 100644
--- a/src/Contracts/Codec/CodecMatcherInterface.php
+++ b/src/Contracts/Codec/CodecMatcherInterface.php
@@ -56,6 +56,15 @@ public function registerDecoder(MediaTypeInterface $mediaType, Closure $decoderC
      */
     public function getEncoder();
 
+    /**
+     * Set encoder.
+     *
+     * @param EncoderInterface|Closure $encoder
+     *
+     * @return void
+     */
+    public function setEncoder($encoder);
+
     /**
      * Get decoder.
      *
@@ -63,6 +72,15 @@ public function getEncoder();
      */
     public function getDecoder();
 
+    /**
+     * Set decoder.
+     *
+     * @param DecoderInterface|Closure $decoder
+     *
+     * @return DecoderInterface
+     */
+    public function setDecoder($decoder);
+
     /**
      * Find best encoder match for 'Accept' header.
      *
diff --git a/src/Contracts/Document/DocumentFactoryInterface.php b/src/Contracts/Document/DocumentFactoryInterface.php
index f643b1b9..d567a31c 100644
--- a/src/Contracts/Document/DocumentFactoryInterface.php
+++ b/src/Contracts/Document/DocumentFactoryInterface.php
@@ -16,6 +16,8 @@
  * limitations under the License.
  */
 
+use \Neomerx\JsonApi\Contracts\Schema\LinkInterface;
+
 /**
  * @package Neomerx\JsonApi
  */
@@ -31,20 +33,20 @@ public function createDocument();
     /**
      * Create error instance.
      *
-     * @param int|string|null $idx
-     * @param string|null     $href
-     * @param string|null     $status
-     * @param string|null     $code
-     * @param string|null     $title
-     * @param string|null     $detail
-     * @param mixed|null      $source
-     * @param array|null      $meta
+     * @param int|string|null    $idx
+     * @param LinkInterface|null $aboutLink
+     * @param string|null        $status
+     * @param string|null        $code
+     * @param string|null        $title
+     * @param string|null        $detail
+     * @param mixed|null         $source
+     * @param array|null         $meta
      *
      * @return ErrorInterface
      */
     public function createError(
         $idx = null,
-        $href = null,
+        LinkInterface $aboutLink = null,
         $status = null,
         $code = null,
         $title = null,
diff --git a/src/Contracts/Document/DocumentInterface.php b/src/Contracts/Document/DocumentInterface.php
index 15f1ad20..64ef7643 100644
--- a/src/Contracts/Document/DocumentInterface.php
+++ b/src/Contracts/Document/DocumentInterface.php
@@ -57,13 +57,17 @@ interface DocumentInterface
     const KEYWORD_DATA          = 'data';
     /** Reserved keyword */
     const KEYWORD_INCLUDED      = 'included';
+    /** Reserved keyword */
+    const KEYWORD_JSON_API      = 'jsonapi';
+    /** Reserved keyword */
+    const KEYWORD_VERSION       = 'version';
 
     /** Reserved keyword */
     const KEYWORD_ERRORS        = 'errors';
     /** Reserved keyword */
     const KEYWORD_ERRORS_ID     = 'id';
     /** Reserved keyword */
-    const KEYWORD_ERRORS_HREF   = 'href';
+    const KEYWORD_ERRORS_LINKS  = self::KEYWORD_LINKS;
     /** Reserved keyword */
     const KEYWORD_ERRORS_STATUS = 'status';
     /** Reserved keyword */
@@ -76,11 +80,13 @@ interface DocumentInterface
     const KEYWORD_ERRORS_META   = 'meta';
     /** Reserved keyword */
     const KEYWORD_ERRORS_SOURCE = 'source';
+    /** Reserved keyword */
+    const KEYWORD_ERRORS_ABOUT  = 'about';
 
     /**
      * Set URLs to top-level 'links' section.
      *
-     * @param LinkInterface[]|null $links
+     * @param array<string,LinkInterface>|null $links
      *
      * @return void
      */
@@ -133,16 +139,6 @@ public function addRelationshipToData(
         ResourceObjectInterface $resource
     );
 
-    /**
-     * Add a reference to resource in 'data' section.
-     *
-     * @param ResourceObjectInterface     $parent
-     * @param RelationshipObjectInterface $current
-     *
-     * @return void
-     */
-    public function addReferenceToData(ResourceObjectInterface $parent, RelationshipObjectInterface $current);
-
     /**
      * Add an empty relationship to resource in 'data' section.
      *
@@ -187,16 +183,6 @@ public function addRelationshipToIncluded(
         ResourceObjectInterface $resource
     );
 
-    /**
-     * Add a reference to resource in 'included' section.
-     *
-     * @param ResourceObjectInterface     $parent
-     * @param RelationshipObjectInterface $current
-     *
-     * @return void
-     */
-    public function addReferenceToIncluded(ResourceObjectInterface $parent, RelationshipObjectInterface $current);
-
     /**
      * Add an empty relationship to resource in 'included' section.
      *
@@ -243,6 +229,29 @@ public function setResourceCompleted(ResourceObjectInterface $resource);
      */
     public function addError(ErrorInterface $error);
 
+    /**
+     * Add JSON API version information.
+     *
+     * @link http://jsonapi.org/format/#document-jsonapi-object
+     *
+     * @param string     $version
+     * @param mixed|null $meta
+     *
+     * @return void
+     */
+    public function addJsonApiVersion($version, $meta = null);
+
+    /**
+     * Set a prefix that will be applied to all URLs in the document except marked as href.
+     *
+     * @see LinkInterface
+     *
+     * @param string $prefix
+     *
+     * @return void
+     */
+    public function setUrlPrefix($prefix);
+
     /**
      * Remove 'data' top-level section.
      *
diff --git a/src/Contracts/Document/ErrorInterface.php b/src/Contracts/Document/ErrorInterface.php
index 341325dd..738e2f03 100644
--- a/src/Contracts/Document/ErrorInterface.php
+++ b/src/Contracts/Document/ErrorInterface.php
@@ -22,18 +22,18 @@
 interface ErrorInterface
 {
     /**
-     * Get a  unique identifier for this particular occurrence of the problem.
+     * Get a unique identifier for this particular occurrence of the problem.
      *
      * @return int|string|null
      */
     public function getId();
 
     /**
-     * Get a URI that may yield further details about this particular occurrence of the problem.
+     * Get links that may lead to further details about this particular occurrence of the problem.
      *
-     * @return string|null
+     * @return null|array<string,\Neomerx\JsonApi\Contracts\Schema\LinkInterface>
      */
-    public function getHref();
+    public function getLinks();
 
     /**
      * Get the HTTP status code applicable to this problem, expressed as a string value.
diff --git a/src/Contracts/Encoder/EncoderInterface.php b/src/Contracts/Encoder/EncoderInterface.php
index 03ca080b..8c7d1efb 100644
--- a/src/Contracts/Encoder/EncoderInterface.php
+++ b/src/Contracts/Encoder/EncoderInterface.php
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-use \Neomerx\JsonApi\Contracts\Schema\LinkInterface;
+use \Neomerx\JsonApi\Encoder\EncoderOptions;
 use \Neomerx\JsonApi\Contracts\Document\ErrorInterface;
 use \Neomerx\JsonApi\Contracts\Parameters\EncodingParametersInterface;
 
@@ -25,11 +25,14 @@
  */
 interface EncoderInterface
 {
+    /** JSON API version implemented by the encoder */
+    const JSON_API_VERSION = '1.0';
+
     /**
      * Encode input as JSON API string.
      *
      * @param object|array                     $data     Data to encode.
-     * @param LinkInterface[]|null             $links    Optional document links information (e.g. request URL, paging).
+     * @param array<string,LinkInterface>|null $links    Optional document links information (e.g. request URL, paging).
      * @param array|object|null                $meta     Optional document meta information.
      * @param EncodingParametersInterface|null $parameters Encoding parameters.
      *
@@ -63,4 +66,11 @@ public function error(ErrorInterface $error);
      * @return string
      */
     public function errors($errors);
+
+    /**
+     * Get encoder options.
+     *
+     * @return EncoderOptions|null
+     */
+    public function getEncoderOptions();
 }
diff --git a/src/Contracts/Encoder/Parser/ParserReplyInterface.php b/src/Contracts/Encoder/Parser/ParserReplyInterface.php
index 43f3709a..de387193 100644
--- a/src/Contracts/Encoder/Parser/ParserReplyInterface.php
+++ b/src/Contracts/Encoder/Parser/ParserReplyInterface.php
@@ -25,14 +25,12 @@ interface ParserReplyInterface
 {
     /** Indicates resource description started */
     const REPLY_TYPE_RESOURCE_STARTED       = 0;
-    /** Indicates resource description completed */
+    /** Indicates resource description started */
     const REPLY_TYPE_NULL_RESOURCE_STARTED  = 1;
-    /** Indicates resource description completed */
+    /** Indicates resource description started */
     const REPLY_TYPE_EMPTY_RESOURCE_STARTED = 2;
     /** Indicates resource description completed */
-    const REPLY_TYPE_REFERENCE_STARTED      = 3;
-    /** Indicates resource description completed */
-    const REPLY_TYPE_RESOURCE_COMPLETED     = 4;
+    const REPLY_TYPE_RESOURCE_COMPLETED     = 3;
 
     /**
      * Get reply type.
diff --git a/src/Contracts/Integration/CurrentRequestInterface.php b/src/Contracts/Integration/CurrentRequestInterface.php
index 3d946862..33f7db72 100644
--- a/src/Contracts/Integration/CurrentRequestInterface.php
+++ b/src/Contracts/Integration/CurrentRequestInterface.php
@@ -40,7 +40,7 @@ public function getQueryParameters();
      *
      * @param string $name
      *
-     * @return string|array|null
+     * @return string
      */
     public function getHeader($name);
 }
diff --git a/src/Contracts/Integration/ExceptionThrowerInterface.php b/src/Contracts/Integration/ExceptionThrowerInterface.php
index a3bfe43d..4eac82fd 100644
--- a/src/Contracts/Integration/ExceptionThrowerInterface.php
+++ b/src/Contracts/Integration/ExceptionThrowerInterface.php
@@ -28,6 +28,12 @@ interface ExceptionThrowerInterface
      */
     public function throwBadRequest();
 
+    /**
+     * Throw 'Forbidden' exception (HTTP code 403).
+     *
+     * @return void
+     */
+    public function throwForbidden();
     /**
      * Throw 'Not Acceptable' exception (HTTP code 406).
      *
@@ -35,6 +41,13 @@ public function throwBadRequest();
      */
     public function throwNotAcceptable();
 
+    /**
+     * Throw 'Conflict' exception (HTTP code 409).
+     *
+     * @return void
+     */
+    public function throwConflict();
+
     /**
      * Throw 'Unsupported Media Type' exception (HTTP code 415).
      *
diff --git a/src/Contracts/Parameters/ParametersInterface.php b/src/Contracts/Parameters/ParametersInterface.php
index 27584425..0a68d825 100644
--- a/src/Contracts/Parameters/ParametersInterface.php
+++ b/src/Contracts/Parameters/ParametersInterface.php
@@ -71,4 +71,11 @@ public function getFilteringParameters();
      * @return array|null
      */
     public function getUnrecognizedParameters();
+
+    /**
+     * Returns true if inclusion, field set, sorting, paging, filtering and unrecognized parameters are empty.
+     *
+     * @return bool
+     */
+    public function isEmpty();
 }
diff --git a/src/Contracts/Parameters/ParametersParserInterface.php b/src/Contracts/Parameters/ParametersParserInterface.php
index a91d1974..1f1ce787 100644
--- a/src/Contracts/Parameters/ParametersParserInterface.php
+++ b/src/Contracts/Parameters/ParametersParserInterface.php
@@ -24,6 +24,21 @@
  */
 interface ParametersParserInterface
 {
+    /** Parameter name */
+    const PARAM_INCLUDE = 'include';
+
+    /** Parameter name */
+    const PARAM_FIELDS = 'fields';
+
+    /** Parameter name */
+    const PARAM_PAGE = 'page';
+
+    /** Parameter name */
+    const PARAM_FILTER = 'filter';
+
+    /** Parameter name */
+    const PARAM_SORT = 'sort';
+
     /**
      * Parse input parameters from request.
      *
diff --git a/src/Contracts/Schema/LinkInterface.php b/src/Contracts/Schema/LinkInterface.php
index 889ded07..570e09f8 100644
--- a/src/Contracts/Schema/LinkInterface.php
+++ b/src/Contracts/Schema/LinkInterface.php
@@ -51,4 +51,11 @@ public function getSubHref();
      * @return array|object|null
      */
     public function getMeta();
+
+    /**
+     * If $subHref is a full URL and must not be concatenated with other URLs.
+     *
+     * @return bool
+     */
+    public function isTreatAsHref();
 }
diff --git a/src/Contracts/Schema/RelationshipObjectInterface.php b/src/Contracts/Schema/RelationshipObjectInterface.php
index d444d48e..b261a359 100644
--- a/src/Contracts/Schema/RelationshipObjectInterface.php
+++ b/src/Contracts/Schema/RelationshipObjectInterface.php
@@ -29,32 +29,25 @@ interface RelationshipObjectInterface
     public function getName();
 
     /**
-     * If 'self' endpoint URL should be shown.
-     *
-     * @return bool
-     */
-    public function isShowSelf();
-
-    /**
-     * Get 'self' URL of link object.
+     * Get resource data from relationship.
      *
-     * @return LinkInterface
+     * @return object|array|null
      */
-    public function getSelfLink();
+    public function getData();
 
     /**
-     * If link should be shown as URL reference ('related').
+     * Get links.
      *
-     * @return bool
+     * @return array<string,\Neomerx\JsonApi\Contracts\Schema\LinkInterface>
      */
-    public function isShowAsReference();
+    public function getLinks();
 
     /**
-     * If 'related' endpoint URL should be shown.
+     * Get meta.
      *
-     * @return bool
+     * @return mixed
      */
-    public function isShowRelated();
+    public function getMeta();
 
     /**
      * If 'data' should be shown.
@@ -62,39 +55,4 @@ public function isShowRelated();
      * @return bool
      */
     public function isShowData();
-
-    /**
-     * Get 'related' URL of link object.
-     *
-     * @return LinkInterface
-     */
-    public function getRelatedLink();
-
-    /**
-     * If 'meta' should be shown.
-     *
-     * @return bool
-     */
-    public function isShowMeta();
-
-    /**
-     * If pagination information should be shown.
-     *
-     * @return bool
-     */
-    public function isShowPagination();
-
-    /**
-     * Get resource data from relationship.
-     *
-     * @return object|array|null
-     */
-    public function getData();
-
-    /**
-     * Get pagination information.
-     *
-     * @return LinkInterface[]|null
-     */
-    public function getPagination();
 }
diff --git a/src/Contracts/Schema/ResourceObjectInterface.php b/src/Contracts/Schema/ResourceObjectInterface.php
index 5303c78c..0a9f74cf 100644
--- a/src/Contracts/Schema/ResourceObjectInterface.php
+++ b/src/Contracts/Schema/ResourceObjectInterface.php
@@ -47,49 +47,63 @@ public function getAttributes();
      *
      * @return array|object|null
      */
-    public function getMeta();
+    public function getPrimaryMeta();
 
     /**
-     * Get resource 'self' endpoint URL.
+     * Get meta-information about resource relationships when resource is primary.
      *
-     * @return string
+     * @return array|object|null
      */
-    public function getSelfUrl();
+    public function getRelationshipsPrimaryMeta();
 
     /**
-     * If 'self' endpoint URL should be shown for resource in 'data' section.
+     * Get meta-information about resource object.
      *
-     * @return bool
+     * @return array|object|null
      */
-    public function isShowSelf();
+    public function getLinkageMeta();
 
     /**
-     * If 'meta' should be shown for resource in 'data' section.
+     * Get meta-information about resource object.
      *
-     * @return bool
+     * @return array|object|null
      */
-    public function isShowMeta();
+    public function getInclusionMeta();
 
     /**
-     * If 'self' endpoint URL should be shown for included resources.
+     * Get meta-information about resource relationships when resource is included.
+     *
+     * @return array|object|null
+     */
+    public function getRelationshipsInclusionMeta();
+
+    /**
+     * Get resource 'self' endpoint URL.
+     *
+     * @return LinkInterface
+     */
+    public function getSelfSubLink();
+
+    /**
+     * If 'self' endpoint URL should be shown for resource in 'data' section.
      *
      * @return bool
      */
-    public function isShowSelfInIncluded();
+    public function isShowSelf();
 
     /**
-     * If 'meta' should be shown for included resources.
+     * If 'self' endpoint URL should be shown for included resources.
      *
      * @return bool
      */
-    public function isShowMetaInIncluded();
+    public function isShowSelfInIncluded();
 
     /**
-     * If 'meta' should be shown in relationships.
+     * If resource attributes should be shown when the resource is within 'included'.
      *
      * @return bool
      */
-    public function isShowMetaInRelationships();
+    public function isShowAttributesInIncluded();
 
     /**
      * If resource relationships should be shown for included resources.
diff --git a/src/Contracts/Schema/SchemaFactoryInterface.php b/src/Contracts/Schema/SchemaFactoryInterface.php
index f751cebc..8b239b7b 100644
--- a/src/Contracts/Schema/SchemaFactoryInterface.php
+++ b/src/Contracts/Schema/SchemaFactoryInterface.php
@@ -33,66 +33,32 @@ public function createContainer(array $providers = []);
     /**
      * Create resource object.
      *
-     * @param bool   $isInArray
-     * @param string $type
-     * @param string $idx
-     * @param array  $attributes
-     * @param mixed  $meta
-     * @param string $selfUrl
-     * @param bool   $isShowSelf
-     * @param bool   $isShowMeta
-     * @param bool   $isShowSelfInIncluded
-     * @param bool   $isShowRelShipsInIncluded
-     * @param bool   $isShowMetaInIncluded
-     * @param bool   $isShowMetaInRlShips
+     * @param SchemaProviderInterface $schema
+     * @param object                  $resource
+     * @param bool                    $isInArray
+     * @param array<string,int>|null  $attributeKeysFilter
      *
      * @return ResourceObjectInterface
      */
     public function createResourceObject(
+        SchemaProviderInterface $schema,
+        $resource,
         $isInArray,
-        $type,
-        $idx,
-        array $attributes,
-        $meta,
-        $selfUrl,
-        $isShowSelf,
-        $isShowMeta,
-        $isShowSelfInIncluded,
-        $isShowRelShipsInIncluded,
-        $isShowMetaInIncluded,
-        $isShowMetaInRlShips
+        $attributeKeysFilter = null
     );
 
     /**
      * Create relationship object.
      *
-     * @param string               $name
-     * @param object|array|null    $data
-     * @param LinkInterface        $selfLink
-     * @param LinkInterface        $relatedLink
-     * @param bool                 $isShowAsRef
-     * @param bool                 $isShowSelf
-     * @param bool                 $isShowRelated
-     * @param bool                 $isShowData
-     * @param bool                 $isShowMeta
-     * @param bool                 $isShowPagination
-     * @param LinkInterface[]|null $pagination
+     * @param string                                                        $name
+     * @param object|array|null                                             $data
+     * @param array<string,\Neomerx\JsonApi\Contracts\Schema\LinkInterface> $links
+     * @param mixed                                                         $meta
+     * @param bool                                                          $isShowData
      *
      * @return RelationshipObjectInterface
      */
-    public function createRelationshipObject(
-        $name,
-        $data,
-        LinkInterface $selfLink,
-        LinkInterface $relatedLink,
-        $isShowAsRef,
-        $isShowSelf,
-        $isShowRelated,
-        $isShowData,
-        $isShowMeta,
-        $isShowPagination,
-        $pagination
-    );
+    public function createRelationshipObject($name, $data, $links, $meta, $isShowData);
 
     /**
      * Create link.
diff --git a/src/Contracts/Schema/SchemaProviderInterface.php b/src/Contracts/Schema/SchemaProviderInterface.php
index dddb969f..63280d56 100644
--- a/src/Contracts/Schema/SchemaProviderInterface.php
+++ b/src/Contracts/Schema/SchemaProviderInterface.php
@@ -44,9 +44,9 @@ public function getId($resource);
      *
      * @param object $resource
      *
-     * @return string
+     * @return LinkInterface
      */
-    public function getSelfUrl($resource);
+    public function getSelfSubLink($resource);
 
     /**
      * Get resource attributes.
@@ -58,22 +58,24 @@ public function getSelfUrl($resource);
     public function getAttributes($resource);
 
     /**
-     * Get resource's relationship objects.
+     * Create resource object.
      *
-     * @param object $resource
+     * @param object                   $resource
+     * @param bool                     $isOriginallyArrayed
+     * @param array <string, int>|null $attributeKeysFilter
      *
-     * @return Iterator LinkObjectInterface[]
+     * @return ResourceObjectInterface
      */
-    public function getRelationshipObjectIterator($resource);
+    public function createResourceObject($resource, $isOriginallyArrayed, $attributeKeysFilter = null);
 
     /**
-     * Get resource meta information.
+     * Get resource's relationship objects.
      *
      * @param object $resource
      *
-     * @return mixed
+     * @return Iterator LinkObjectInterface[]
      */
-    public function getMeta($resource);
+    public function getRelationshipObjectIterator($resource);
 
     /**
      * If 'self' endpoint URL.
@@ -83,18 +85,18 @@ public function getMeta($resource);
     public function isShowSelf();
 
     /**
-     * If 'meta' should be shown for resource.
+     * If 'self' endpoint URL should be shown for included resources.
      *
      * @return bool
      */
-    public function isShowMeta();
+    public function isShowSelfInIncluded();
 
     /**
-     * If 'self' endpoint URL should be shown for included resources.
+     * If resource attributes should be shown when the resource is within 'included'.
      *
      * @return bool
      */
-    public function isShowSelfInIncluded();
+    public function isShowAttributesInIncluded();
 
     /**
      * If links be shown for included resources.
@@ -104,34 +106,54 @@ public function isShowSelfInIncluded();
     public function isShowRelationshipsInIncluded();
 
     /**
-     * If 'meta' should be shown for included resources.
+     * Get schema default include paths.
      *
-     * @return bool
+     * @return string[]
      */
-    public function isShowMetaInIncluded();
+    public function getIncludePaths();
 
     /**
-     * If 'meta' should be shown in relationships.
+     * Get meta when resource is primary (top level 'data' section).
      *
-     * @return bool
+     * @param object $resource
+     *
+     * @return mixed
      */
-    public function isShowMetaInRelationships();
+    public function getPrimaryMeta($resource);
 
     /**
-     * Create resource object.
+     * Get meta when resource is within included resources.
      *
      * @param object $resource
-     * @param bool   $isOriginallyArrayed
-     * @param        array <string, int>|null $attributeKeysFilter
      *
-     * @return ResourceObjectInterface
+     * @return mixed
      */
-    public function createResourceObject($resource, $isOriginallyArrayed, array $attributeKeysFilter = null);
+    public function getInclusionMeta($resource);
 
     /**
-     * Get schema default include paths.
+     * Get get relationships meta when the resource is primary.
      *
-     * @return string[]
+     * @param object $resource
+     *
+     * @return mixed
      */
-    public function getIncludePaths();
+    public function getRelationshipsPrimaryMeta($resource);
+
+    /**
+     * Get get relationships meta when the resource is within included.
+     *
+     * @param object $resource
+     *
+     * @return mixed
+     */
+    public function getRelationshipsInclusionMeta($resource);
+
+    /**
+     * Get meta when resource is within relationship of a primary resource.
+     *
+     * @param object $resource
+     *
+     * @return mixed
+     */
+    public function getLinkageMeta($resource);
 }
diff --git a/src/Document/Document.php b/src/Document/Document.php
index 73672dfd..ea67f24c 100644
--- a/src/Document/Document.php
+++ b/src/Document/Document.php
@@ -38,7 +38,7 @@ class Document implements DocumentInterface
     private $meta;
 
     /**
-     * @var array
+     * @var array|null|string
      */
     private $links;
 
@@ -48,10 +48,15 @@ class Document implements DocumentInterface
     private $isIncludedMarks;
 
     /**
-     * @var array
+     * @var array|null
      */
     private $included;
 
+    /**
+     * @var array|null
+     */
+    private $version;
+
     /**
      * @var array|null
      */
@@ -84,12 +89,17 @@ class Document implements DocumentInterface
      */
     private $showData = true;
 
+    /**
+     * @var string|null
+     */
+    private $urlPrefix;
+
     /**
      * Constructor.
      */
     public function __construct()
     {
-        $this->presenter = new ElementPresenter();
+        $this->presenter = new ElementPresenter($this);
     }
 
     /**
@@ -97,7 +107,7 @@ public function __construct()
      */
     public function setDocumentLinks($links)
     {
-        $links === null ?: $this->links = $this->presenter->getLinksRepresentation($links);
+        $this->links = $this->presenter->getLinksRepresentation($this->urlPrefix, $links);
     }
 
     /**
@@ -138,7 +148,7 @@ public function addToData(ResourceObjectInterface $resource)
         $idx  = $resource->getId();
         $type = $resource->getType();
         assert('isset($this->bufferForData[$type][$idx]) === false');
-        $this->bufferForData[$type][$idx] = $this->presenter->convertDataResourceToArray($resource);
+        $this->bufferForData[$type][$idx] = $this->presenter->convertDataResourceToArray($resource, true);
     }
 
     /**
@@ -179,24 +189,6 @@ public function addRelationshipToIncluded(
         $this->presenter->addRelationshipTo($this->bufferForIncluded, $parent, $relationship, $resource);
     }
 
-    /**
-     * @inheritdoc
-     */
-    public function addReferenceToData(ResourceObjectInterface $parent, RelationshipObjectInterface $current)
-    {
-        $url = $this->presenter->concatUrls($parent->getSelfUrl(), $current->getRelatedLink());
-        $this->presenter->setRelationshipTo($this->bufferForData, $parent, $current, $url);
-    }
-
-    /**
-     * @inheritdoc
-     */
-    public function addReferenceToIncluded(ResourceObjectInterface $parent, RelationshipObjectInterface $current)
-    {
-        $url = $this->presenter->concatUrls($parent->getSelfUrl(), $current->getRelatedLink());
-        $this->presenter->setRelationshipTo($this->bufferForIncluded, $parent, $current, $url);
-    }
-
     /**
      * @inheritdoc
      */
@@ -243,12 +235,22 @@ public function setResourceCompleted(ResourceObjectInterface $resource)
         $foundInIncluded = isset($this->bufferForIncluded[$type][$idx]);
 
         if ($foundInData === true) {
-            $this->data[] = $this->presenter->correctRelationships($this->bufferForData[$type][$idx]);
+            $representation = $this->presenter->correctRelationships($this->bufferForData[$type][$idx]);
+            $relShipsMeta   = $resource->getRelationshipsPrimaryMeta();
+            if (empty($relShipsMeta) === false && isset($representation[self::KEYWORD_RELATIONSHIPS]) === true) {
+                $representation[self::KEYWORD_RELATIONSHIPS][self::KEYWORD_META] = $relShipsMeta;
+            }
+            $this->data[]   = $representation;
             unset($this->bufferForData[$type][$idx]);
         }
 
         if ($foundInIncluded === true) {
-            $this->included[] = $this->presenter->correctRelationships($this->bufferForIncluded[$type][$idx]);
+            $representation   = $this->presenter->correctRelationships($this->bufferForIncluded[$type][$idx]);
+            $relShipsMeta   = $resource->getRelationshipsInclusionMeta();
+            if (empty($relShipsMeta) === false && isset($representation[self::KEYWORD_RELATIONSHIPS]) === true) {
+                $representation[self::KEYWORD_RELATIONSHIPS][self::KEYWORD_META] = $relShipsMeta;
+            }
+            $this->included[] = $representation;
             unset($this->bufferForIncluded[$type][$idx]);
         }
     }
@@ -263,6 +265,7 @@ public function getDocument()
         }
 
         $document = array_filter([
+            self::KEYWORD_JSON_API => $this->version,
             self::KEYWORD_META     => $this->meta,
             self::KEYWORD_LINKS    => $this->links,
             self::KEYWORD_DATA     => true, // this field wont be filtered
@@ -281,6 +284,15 @@ public function getDocument()
         return $document;
     }
 
+    /**
+     * @inheritdoc
+     */
+    public function addJsonApiVersion($version, $meta = null)
+    {
+        $this->version = $meta === null ?
+            [self::KEYWORD_VERSION => $version] : [self::KEYWORD_VERSION => $version, self::KEYWORD_META => $meta];
+    }
+
     /**
      * @inheritdoc
      */
@@ -298,7 +310,8 @@ public function addError(ErrorInterface $error)
 
         $representation = array_filter([
             self::KEYWORD_ERRORS_ID     => $errorId,
-            self::KEYWORD_ERRORS_HREF   => $error->getHref(),
+            self::KEYWORD_ERRORS_LINKS  => $this->presenter
+                ->getLinksRepresentation($this->urlPrefix, $error->getLinks()),
             self::KEYWORD_ERRORS_STATUS => $error->getStatus(),
             self::KEYWORD_ERRORS_CODE   => $error->getCode(),
             self::KEYWORD_ERRORS_TITLE  => $error->getTitle(),
@@ -311,4 +324,22 @@ public function addError(ErrorInterface $error)
 
         $this->errors[] = $representation;
     }
+
+    /**
+     * @inheritdoc
+     */
+    public function setUrlPrefix($prefix)
+    {
+        $this->urlPrefix = (string)$prefix;
+    }
+
+    /**
+     * Get URL prefix.
+     *
+     * @return null|string
+     */
+    public function getUrlPrefix()
+    {
+        return $this->urlPrefix;
+    }
 }
diff --git a/src/Document/DocumentFactory.php b/src/Document/DocumentFactory.php
index 5f9a78b7..faf6b6c6 100644
--- a/src/Document/DocumentFactory.php
+++ b/src/Document/DocumentFactory.php
@@ -16,6 +16,7 @@
  * limitations under the License.
  */
 
+use \Neomerx\JsonApi\Contracts\Schema\LinkInterface;
 use \Neomerx\JsonApi\Contracts\Document\DocumentFactoryInterface;
 
 /**
@@ -36,7 +37,7 @@ public function createDocument()
      */
     public function createError(
         $idx = null,
-        $href = null,
+        LinkInterface $aboutLink = null,
         $status = null,
         $code = null,
         $title = null,
@@ -44,6 +45,6 @@ public function createError(
         $source = null,
         array $meta = null
     ) {
-        return new Error($idx, $href, $status, $code, $title, $detail, $source, $meta);
+        return new Error($idx, $aboutLink, $status, $code, $title, $detail, $source, $meta);
     }
 }
diff --git a/src/Document/Error.php b/src/Document/Error.php
index 652f94de..1cc09b5b 100644
--- a/src/Document/Error.php
+++ b/src/Document/Error.php
@@ -16,7 +16,9 @@
  * limitations under the License.
  */
 
+use \Neomerx\JsonApi\Contracts\Schema\LinkInterface;
 use \Neomerx\JsonApi\Contracts\Document\ErrorInterface;
+use \Neomerx\JsonApi\Contracts\Document\DocumentInterface;
 
 /**
  * @package Neomerx\JsonApi
@@ -29,9 +31,9 @@ class Error implements ErrorInterface
     private $idx;
 
     /**
-     * @var string|null
+     * @var null|array<string,\Neomerx\JsonApi\Contracts\Schema\LinkInterface>
      */
-    private $href;
+    private $links;
 
     /**
      * @var string|null
@@ -64,18 +66,18 @@ class Error implements ErrorInterface
     private $meta;
 
     /**
-     * @param int|string|null $idx
-     * @param string|null     $href
-     * @param string|null     $status
-     * @param string|null     $code
-     * @param string|null     $title
-     * @param string|null     $detail
-     * @param array|null      $source
-     * @param array|null      $meta
+     * @param int|string|null    $idx
+     * @param LinkInterface|null $aboutLink
+     * @param string|null        $status
+     * @param string|null        $code
+     * @param string|null        $title
+     * @param string|null        $detail
+     * @param array|null         $source
+     * @param array|null         $meta
      */
     public function __construct(
         $idx = null,
-        $href = null,
+        LinkInterface $aboutLink = null,
         $status = null,
         $code = null,
         $title = null,
@@ -83,17 +85,15 @@ public function __construct(
         array $source = null,
         array $meta = null
     ) {
-        assert('$idx === null     || is_int($idx) || is_string($idx)');
-        assert('$href === null    || is_string($href)');
-        assert('$status === null  || is_string($status)');
-        assert('$code === null    || is_string($code)');
-        assert('$title === null   || is_string($title)');
-        assert('$title === null   || is_string($title)');
-        assert('$detail === null  || is_string($detail)');
-        assert('$meta === null    || is_array($meta)');
+        assert(
+            '($idx === null || is_int($idx) || is_string($idx)) &&'.
+            '($status === null || is_string($status)) && ($code === null || is_string($code)) &&'.
+            '($title === null  || is_string($title)) && ($title === null || is_string($title)) &&'.
+            '($detail === null || is_string($detail)) && ($meta === null || is_array($meta))'
+        );
 
         $this->idx     = $idx;
-        $this->href    = $href;
+        $this->links   = ($aboutLink === null ? null : [DocumentInterface::KEYWORD_ERRORS_ABOUT => $aboutLink]);
         $this->status  = $status;
         $this->code    = $code;
         $this->title   = $title;
@@ -113,9 +113,9 @@ public function getId()
     /**
      * @inheritdoc
      */
-    public function getHref()
+    public function getLinks()
     {
-        return $this->href;
+        return $this->links;
     }
 
     /**
diff --git a/src/Document/Presenters/ElementPresenter.php b/src/Document/Presenters/ElementPresenter.php
index 09745a3b..d6bfcb43 100644
--- a/src/Document/Presenters/ElementPresenter.php
+++ b/src/Document/Presenters/ElementPresenter.php
@@ -28,6 +28,19 @@
  */
 class ElementPresenter
 {
+    /**
+     * @var Document
+     */
+    private $document;
+
+    /**
+     * @param Document $document
+     */
+    public function __construct(Document $document)
+    {
+        $this->document = $document;
+    }
+
     /**
      * @param array                       $target
      * @param ResourceObjectInterface     $parent
@@ -120,12 +133,18 @@ public function correctRelationships(array $resource)
      * Convert resource object for 'data' section to array.
      *
      * @param ResourceObjectInterface $resource
+     * @param bool                    $isShowAttributes
      *
      * @return array
      */
-    public function convertDataResourceToArray(ResourceObjectInterface $resource)
+    public function convertDataResourceToArray(ResourceObjectInterface $resource, $isShowAttributes)
     {
-        return $this->convertResourceToArray($resource, $resource->isShowSelf(), $resource->isShowMeta());
+        return $this->convertResourceToArray(
+            $resource,
+            $resource->isShowSelf(),
+            $resource->getPrimaryMeta(),
+            $isShowAttributes
+        );
     }
 
     /**
@@ -137,45 +156,27 @@ public function convertDataResourceToArray(ResourceObjectInterface $resource)
      */
     public function convertIncludedResourceToArray(ResourceObjectInterface $resource)
     {
-        return $this
-            ->convertResourceToArray($resource, $resource->isShowSelfInIncluded(), $resource->isShowMetaInIncluded());
-    }
-
-    /**
-     * @param string        $url
-     * @param LinkInterface $subLink
-     *
-     * @return string|array
-     */
-    public function concatUrls($url, LinkInterface $subLink)
-    {
-        $subUrl = $subLink->getSubHref();
-
-        $urlEndsWithSlash   = (substr($url, -1) === '/');
-        $subStartsWithSlash = (substr($subUrl, 0, 1) === '/');
-        if ($urlEndsWithSlash === false && $subStartsWithSlash === false) {
-            $resultUrl = $url . '/' . $subUrl;
-        } elseif (($urlEndsWithSlash xor $subStartsWithSlash) === true) {
-            $resultUrl = $url . $subUrl;
-        } else {
-            $resultUrl = rtrim($url, '/') . $subUrl;
-        }
-
-        return $this->getUrlRepresentation($resultUrl, $subLink->getMeta());
+        return $this->convertResourceToArray(
+            $resource,
+            $resource->isShowSelfInIncluded(),
+            $resource->getInclusionMeta(),
+            $resource->isShowAttributesInIncluded()
+        );
     }
 
     /**
-     * @param LinkInterface[]|null $links
+     * @param string|null                                                        $prefix
+     * @param array<string,\Neomerx\JsonApi\Contracts\Schema\LinkInterface>|null $links
      *
-     * @return string|null|array
+     * @return array|null|string
      */
-    public function getLinksRepresentation($links)
+    public function getLinksRepresentation($prefix = null, $links = null)
     {
         $result = null;
-        if ($links !== null) {
+        if (empty($links) === false) {
             foreach ($links as $name => $link) {
                 /** @var LinkInterface $link */
-                $result[$name] = $this->getLinkRepresentation($link);
+                $result[$name] = $this->getLinkRepresentation($prefix, $link);
             }
         }
 
@@ -211,20 +212,24 @@ private function getLinkageRepresentation(ResourceObjectInterface $resource)
             Document::KEYWORD_TYPE => $resource->getType(),
             Document::KEYWORD_ID   => $resource->getId(),
         ];
-        if ($resource->isShowMetaInRelationships() === true) {
-            $representation[Document::KEYWORD_META] = $resource->getMeta();
+        if (($meta = $resource->getLinkageMeta()) !== null) {
+            $representation[Document::KEYWORD_META] = $meta;
         }
         return $representation;
     }
 
     /**
+     * @param string|null        $prefix
      * @param LinkInterface|null $link
      *
-     * @return string|null|array
+     * @return array|null|string
      */
-    private function getLinkRepresentation(LinkInterface $link = null)
+    private function getLinkRepresentation($prefix = null, LinkInterface $link = null)
     {
-        return $link === null ? null : $this->getUrlRepresentation($link->getSubHref(), $link->getMeta());
+        return $link === null ? null : $this->getUrlRepresentation(
+            $link->isTreatAsHref() === true ? $link->getSubHref() : $prefix . $link->getSubHref(),
+            $link->getMeta()
+        );
     }
 
     /**
@@ -244,41 +249,25 @@ private function getRelationRepresentation(
             '"self" is a reserved keyword and cannot be used as a related resource link name'
         );
 
-        $selfUrl = $parent->getSelfUrl();
-
         $representation = [];
-        if ($relation->isShowSelf() === true) {
-            $representation[Document::KEYWORD_LINKS][Document::KEYWORD_SELF] =
-                $this->concatUrls($selfUrl, $relation->getSelfLink());
-        }
-
-        if ($relation->isShowRelated() === true) {
-            $representation[Document::KEYWORD_LINKS][Document::KEYWORD_RELATED] =
-                $this->concatUrls($selfUrl, $relation->getRelatedLink());
-        }
 
         if ($relation->isShowData() === true) {
             $representation[Document::KEYWORD_LINKAGE_DATA][] = $this->getLinkageRepresentation($resource);
         }
 
-        if ($relation->isShowMeta() === true) {
-            $representation[Document::KEYWORD_META] = $resource->getMeta();
+        if (($meta = $relation->getMeta()) !== null) {
+            $representation[Document::KEYWORD_META] = $meta;
         }
 
-        if ($relation->isShowPagination() === true && $relation->getPagination() !== null) {
-            if (empty($representation[Document::KEYWORD_LINKS]) === true) {
-                $representation[Document::KEYWORD_LINKS] =
-                    $this->getLinksRepresentation($relation->getPagination());
-            } else {
-                $representation[Document::KEYWORD_LINKS] +=
-                    $this->getLinksRepresentation($relation->getPagination());
-            }
+        $baseUrl = null;
+        if (($selfSubLink = $parent->getSelfSubLink()) !== null) {
+            $baseUrl = $selfSubLink->isTreatAsHref() === true ? $selfSubLink->getSubHref() . '/' :
+                $this->document->getUrlPrefix() . $selfSubLink->getSubHref() . '/';
         }
 
-        assert(
-            '$relation->isShowSelf()||$relation->isShowRelated()||$relation->isShowData()||$relation->isShowMeta()',
-            'Specification requires at least one of them to be shown'
-        );
+        foreach ($relation->getLinks() as $name => $link) {
+            $representation[Document::KEYWORD_LINKS][$name] = $this->getLinkRepresentation($baseUrl, $link);
+        }
 
         return $representation;
     }
@@ -288,14 +277,13 @@ private function getRelationRepresentation(
      *
      * @param ResourceObjectInterface $resource
      * @param bool                    $isShowSelf
-     * @param bool                    $isShowMeta
+     * @param mixed                   $meta
+     * @param bool                    $isShowAttributes
      *
      * @return array
      */
-    private function convertResourceToArray(ResourceObjectInterface $resource, $isShowSelf, $isShowMeta)
+    private function convertResourceToArray(ResourceObjectInterface $resource, $isShowSelf, $meta, $isShowAttributes)
     {
-        assert('is_bool($isShowSelf) && is_bool($isShowMeta)');
-
         $representation = [
             Document::KEYWORD_TYPE => $resource->getType(),
             Document::KEYWORD_ID   => $resource->getId(),
@@ -307,7 +295,7 @@ private function convertResourceToArray(ResourceObjectInterface $resource, $isSh
             'isset($attributes[\''.Document::KEYWORD_ID.'\']) === false',
             '"type" and "id" are reserved keywords and cannot be used as resource object attributes'
         );
-        if (empty($attributes) === false) {
+        if ($isShowAttributes === true && empty($attributes) === false) {
             $representation[Document::KEYWORD_ATTRIBUTES] = $attributes;
         }
 
@@ -316,11 +304,12 @@ private function convertResourceToArray(ResourceObjectInterface $resource, $isSh
         $representation[Document::KEYWORD_RELATIONSHIPS] = null;
 
         if ($isShowSelf === true) {
-            $representation[Document::KEYWORD_LINKS][Document::KEYWORD_SELF] = $resource->getSelfUrl();
+            $representation[Document::KEYWORD_LINKS][Document::KEYWORD_SELF] =
+                $this->getLinkRepresentation($this->document->getUrlPrefix(), $resource->getSelfSubLink());
         }
 
-        if ($isShowMeta === true) {
-            $representation[Document::KEYWORD_META] = $resource->getMeta();
+        if ($meta !== null) {
+            $representation[Document::KEYWORD_META] = $meta;
         }
 
         return $representation;
diff --git a/src/Encoder/Encoder.php b/src/Encoder/Encoder.php
index acebc31a..b31a5119 100644
--- a/src/Encoder/Encoder.php
+++ b/src/Encoder/Encoder.php
@@ -56,9 +56,9 @@ class Encoder implements EncoderInterface
     private $parametersFactory;
 
     /**
-     * @var JsonEncodeOptions|null
+     * @var EncoderOptions|null
      */
-    protected $encodeOptions;
+    protected $encoderOptions;
 
     /**
      * @param DocumentFactoryInterface   $documentFactory
@@ -66,7 +66,7 @@ class Encoder implements EncoderInterface
      * @param HandlerFactoryInterface    $handlerFactory
      * @param ParametersFactoryInterface $parametersFactory
      * @param ContainerInterface         $container
-     * @param JsonEncodeOptions|null     $encodeOptions
+     * @param EncoderOptions|null        $encoderOptions
      */
     public function __construct(
         DocumentFactoryInterface $documentFactory,
@@ -74,12 +74,12 @@ public function __construct(
         HandlerFactoryInterface $handlerFactory,
         ParametersFactoryInterface $parametersFactory,
         ContainerInterface $container,
-        JsonEncodeOptions $encodeOptions = null
+        EncoderOptions $encoderOptions = null
     ) {
         $this->container         = $container;
-        $this->encodeOptions     = $encodeOptions;
         $this->parserFactory     = $parserFactory;
         $this->handlerFactory    = $handlerFactory;
+        $this->encoderOptions    = $encoderOptions;
         $this->documentFactory   = $documentFactory;
         $this->parametersFactory = $parametersFactory;
     }
@@ -98,6 +98,10 @@ public function encode(
         $parserManager = $this->parserFactory->createManager($parameters);
         $parser        = $this->parserFactory->createParser($this->container, $parserManager);
         $interpreter   = $this->handlerFactory->createReplyInterpreter($docWriter, $parameters);
+
+        $this->encoderOptions !== null && $this->encoderOptions->getUrlPrefix() !== null ?
+            $docWriter->setUrlPrefix($this->encoderOptions->getUrlPrefix()) : null;
+
         foreach ($parser->parse($data) as $reply) {
             $interpreter->handle($reply);
         }
@@ -105,6 +109,10 @@ public function encode(
         $meta  === null ?: $docWriter->setMetaToDocument($meta);
         $links === null ?: $docWriter->setDocumentLinks($links);
 
+        if ($this->encoderOptions !== null && $this->encoderOptions->isShowVersionInfo() === true) {
+            $docWriter->addJsonApiVersion(self::JSON_API_VERSION, $this->encoderOptions->getVersionMeta());
+        }
+
         return $this->encodeToJson($docWriter->getDocument());
     }
 
@@ -147,6 +155,14 @@ public function meta($meta)
         return $this->encodeToJson($docWriter->getDocument());
     }
 
+    /**
+     * @inheritdoc
+     */
+    public function getEncoderOptions()
+    {
+        return $this->encoderOptions;
+    }
+
     /**
      * Encode array to JSON.
      *
@@ -156,20 +172,20 @@ public function meta($meta)
      */
     protected function encodeToJson(array $document)
     {
-        return $this->encodeOptions === null ?
+        return $this->encoderOptions === null ?
             json_encode($document) :
-            json_encode($document, $this->encodeOptions->getOptions(), $this->encodeOptions->getDepth());
+            json_encode($document, $this->encoderOptions->getOptions(), $this->encoderOptions->getDepth());
     }
 
     /**
      * Create encoder instance.
      *
-     * @param array                  $schemas       Schema providers.
-     * @param JsonEncodeOptions|null $encodeOptions
+     * @param array               $schemas       Schema providers.
+     * @param EncoderOptions|null $encodeOptions
      *
      * @return Encoder
      */
-    public static function instance(array $schemas, JsonEncodeOptions $encodeOptions = null)
+    public static function instance(array $schemas, EncoderOptions $encodeOptions = null)
     {
         /** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */
         $schemaFactory = new \Neomerx\JsonApi\Schema\SchemaFactory();
diff --git a/src/Encoder/EncoderOptions.php b/src/Encoder/EncoderOptions.php
new file mode 100644
index 00000000..87c3d23e
--- /dev/null
+++ b/src/Encoder/EncoderOptions.php
@@ -0,0 +1,124 @@
+<?php namespace Neomerx\JsonApi\Encoder;
+
+/**
+ * Copyright 2015 info@neomerx.com (www.neomerx.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @package Neomerx\JsonApi
+ */
+class EncoderOptions
+{
+    /**
+     * @var int
+     */
+    private $options;
+
+    /**
+     * @var int
+     */
+    private $depth;
+
+    /**
+     * @var bool
+     */
+    private $isShowVersionInfo;
+
+    /**
+     * @var mixed|null
+     */
+    private $versionMeta;
+
+    /**
+     * @var null|string
+     */
+    private $urlPrefix;
+
+    /**
+     * @param int         $options
+     * @param string|null $urlPrefix
+     * @param bool        $isShowVersionInfo
+     * @param mixed       $versionMeta
+     * @param int         $depth
+     */
+    public function __construct(
+        $options = 0,
+        $urlPrefix = null,
+        $isShowVersionInfo = false,
+        $versionMeta = null,
+        $depth = 512
+    ) {
+        assert(
+            'is_int($options) && is_int($depth) && ($urlPrefix === null || is_string($urlPrefix)) &&'.
+            ' is_bool($isShowVersionInfo)'
+        );
+
+        $this->options           = $options;
+        $this->depth             = $depth;
+        $this->urlPrefix         = $urlPrefix;
+        $this->isShowVersionInfo = $isShowVersionInfo;
+        $this->versionMeta       = $versionMeta;
+    }
+
+    /**
+     * @link http://php.net/manual/en/function.json-encode.php
+     *
+     * @return int
+     */
+    public function getOptions()
+    {
+        return $this->options;
+    }
+
+    /**
+     * @link http://php.net/manual/en/function.json-encode.php
+     *
+     * @return int
+     */
+    public function getDepth()
+    {
+        return $this->depth;
+    }
+
+    /**
+     * If JSON API version should be rendered on document top level.
+     *
+     * @return bool
+     */
+    public function isShowVersionInfo()
+    {
+        return $this->isShowVersionInfo;
+    }
+
+    /**
+     * Get JSON API meta information for version.
+     *
+     * @link http://jsonapi.org/format/#document-jsonapi-object
+     *
+     * @return mixed|null
+     */
+    public function getVersionMeta()
+    {
+        return $this->versionMeta;
+    }
+
+    /**
+     * @return null|string
+     */
+    public function getUrlPrefix()
+    {
+        return $this->urlPrefix;
+    }
+}
diff --git a/src/Encoder/Handlers/ReplyInterpreter.php b/src/Encoder/Handlers/ReplyInterpreter.php
index 76c0d1b5..9ea55fd4 100644
--- a/src/Encoder/Handlers/ReplyInterpreter.php
+++ b/src/Encoder/Handlers/ReplyInterpreter.php
@@ -168,10 +168,6 @@ private function addRelationshipToData(ParserReplyInterface $reply, Frame $curre
         $parent       = $previous->getResource();
 
         switch($reply->getReplyType()) {
-            case ParserReplyInterface::REPLY_TYPE_REFERENCE_STARTED:
-                assert('$relationship->isShowAsReference() === true');
-                $this->document->addReferenceToData($parent, $relationship);
-                break;
             case ParserReplyInterface::REPLY_TYPE_NULL_RESOURCE_STARTED:
                 $this->document->addNullRelationshipToData($parent, $relationship);
                 break;
@@ -197,10 +193,6 @@ private function addRelationshipToIncluded(ParserReplyInterface $reply, Frame $c
         $parent       = $previous->getResource();
 
         switch($reply->getReplyType()) {
-            case ParserReplyInterface::REPLY_TYPE_REFERENCE_STARTED:
-                assert('$relationship->isShowAsReference() === true');
-                $this->document->addReferenceToIncluded($parent, $relationship);
-                break;
             case ParserReplyInterface::REPLY_TYPE_NULL_RESOURCE_STARTED:
                 $this->document->addNullRelationshipToIncluded($parent, $relationship);
                 break;
@@ -252,6 +244,6 @@ private function isRelationshipInFieldSet(Frame $current, Frame $previous)
             return true;
         }
 
-        return (in_array($current->getRelationship()->getName(), $fieldSet) === true);
+        return (in_array($current->getRelationship()->getName(), $fieldSet, true) === true);
     }
 }
diff --git a/src/Encoder/JsonEncodeOptions.php b/src/Encoder/JsonEncodeOptions.php
deleted file mode 100644
index 7629a93b..00000000
--- a/src/Encoder/JsonEncodeOptions.php
+++ /dev/null
@@ -1,61 +0,0 @@
-<?php namespace Neomerx\JsonApi\Encoder;
-
-/**
- * Copyright 2015 info@neomerx.com (www.neomerx.com)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * @package Neomerx\JsonApi
- */
-class JsonEncodeOptions
-{
-    /**
-     * @var int
-     */
-    private $options;
-
-    /**
-     * @var int
-     */
-    private $depth;
-
-    /**
-     * @param int $options
-     * @param int $depth
-     */
-    public function __construct($options = 0, $depth = 512)
-    {
-        assert('is_int($options) && is_int($depth)');
-
-        $this->options = $options;
-        $this->depth   = $depth;
-    }
-
-    /**
-     * @return int
-     */
-    public function getOptions()
-    {
-        return $this->options;
-    }
-
-    /**
-     * @return int
-     */
-    public function getDepth()
-    {
-        return $this->depth;
-    }
-}
diff --git a/src/Encoder/Parser/Parser.php b/src/Encoder/Parser/Parser.php
index f4474634..a2922ae6 100644
--- a/src/Encoder/Parser/Parser.php
+++ b/src/Encoder/Parser/Parser.php
@@ -128,8 +128,6 @@ private function parseData($data)
 
         if (empty($data) === true) {
             yield $this->createReplyForEmptyData($data);
-        } elseif ($this->isRefFrame()) {
-            yield $this->createReplyForRef();
         } else {
             if (is_array($data) === true) {
                 $isOriginallyArrayed = true;
@@ -143,8 +141,8 @@ private function parseData($data)
             // duplicated are allowed in data however they shouldn't be in includes
             $isDupAllowed = $curFrame->getLevel() < 2;
 
+            $fieldSet = $this->getFieldSet($schema->getResourceType());
             foreach ($data as $resource) {
-                $fieldSet       = $this->getFieldSet($schema->getResourceType());
                 $resourceObject = $schema->createResourceObject($resource, $isOriginallyArrayed, $fieldSet);
                 $isCircular     = $this->checkCircular($resourceObject);
 
@@ -190,16 +188,6 @@ private function createReplyForEmptyData($data)
         return $this->parserFactory->createEmptyReply($replyType, $this->stack);
     }
 
-    /**
-     * @return ParserReplyInterface
-     */
-    private function createReplyForRef()
-    {
-        $replyType = ParserReplyInterface::REPLY_TYPE_REFERENCE_STARTED;
-
-        return $this->parserFactory->createEmptyReply($replyType, $this->stack);
-    }
-
     /**
      * @return ParserReplyInterface
      */
@@ -246,18 +234,6 @@ private function checkCircular(ResourceObjectInterface $resourceObject)
         return false;
     }
 
-    /**
-     * If current stack frame should be shown as a reference.
-     *
-     * @return bool
-     */
-    private function isRefFrame()
-    {
-        /** @var RelationshipObjectInterface $curRelObject */
-        $curRelObject = $this->stack->end()->getRelationship();
-        return ($curRelObject !== null && $curRelObject->isShowAsReference() === true);
-    }
-
     /**
      * @param string $resourceType
      *
diff --git a/src/Encoder/Parser/ParserEmptyReply.php b/src/Encoder/Parser/ParserEmptyReply.php
index 0fb9f551..67852dbc 100644
--- a/src/Encoder/Parser/ParserEmptyReply.php
+++ b/src/Encoder/Parser/ParserEmptyReply.php
@@ -24,17 +24,14 @@
 class ParserEmptyReply extends BaseReply
 {
     /**
-     * @param int                     $replyType
-     * @param StackReadOnlyInterface  $stack
+     * @param int                    $replyType
+     * @param StackReadOnlyInterface $stack
      */
-    public function __construct(
-        $replyType,
-        StackReadOnlyInterface $stack
-    ) {
+    public function __construct($replyType, StackReadOnlyInterface $stack)
+    {
         assert(
             '$replyType === ' . self::REPLY_TYPE_NULL_RESOURCE_STARTED . ' || '.
-            '$replyType === ' . self::REPLY_TYPE_EMPTY_RESOURCE_STARTED . ' || '.
-            '$replyType === ' . self::REPLY_TYPE_REFERENCE_STARTED
+            '$replyType === ' . self::REPLY_TYPE_EMPTY_RESOURCE_STARTED
         );
 
         parent::__construct($replyType, $stack);
diff --git a/src/Encoder/Parser/ParserReply.php b/src/Encoder/Parser/ParserReply.php
index 87af8910..2c1986e3 100644
--- a/src/Encoder/Parser/ParserReply.php
+++ b/src/Encoder/Parser/ParserReply.php
@@ -24,8 +24,8 @@
 class ParserReply extends BaseReply
 {
     /**
-     * @param int                     $replyType
-     * @param StackReadOnlyInterface  $stack
+     * @param int                    $replyType
+     * @param StackReadOnlyInterface $stack
      */
     public function __construct($replyType, StackReadOnlyInterface $stack)
     {
diff --git a/src/Exceptions/RenderContainer.php b/src/Exceptions/RenderContainer.php
index 4fee6555..9ae45fb4 100644
--- a/src/Exceptions/RenderContainer.php
+++ b/src/Exceptions/RenderContainer.php
@@ -20,7 +20,7 @@
 use \Exception;
 use \Neomerx\JsonApi\Encoder\Encoder;
 use \Neomerx\JsonApi\Responses\Responses;
-use \Neomerx\JsonApi\Encoder\JsonEncodeOptions;
+use \Neomerx\JsonApi\Encoder\EncoderOptions;
 use \Neomerx\JsonApi\Contracts\Document\ErrorInterface;
 use \Neomerx\JsonApi\Contracts\Responses\ResponsesInterface;
 use \Neomerx\JsonApi\Contracts\Exceptions\RenderContainerInterface;
@@ -163,12 +163,12 @@ protected function getHttpCodeRender($statusCode)
     protected function getErrorsRender($statusCode)
     {
         /**
-         * @param ErrorInterface[]  $errors
-         * @param JsonEncodeOptions $encodeOptions
+         * @param ErrorInterface[] $errors
+         * @param EncoderOptions   $encodeOptions
          *
          * @return mixed
          */
-        return function (array $errors, JsonEncodeOptions $encodeOptions = null) use ($statusCode) {
+        return function (array $errors, EncoderOptions $encodeOptions = null) use ($statusCode) {
             $extensionsClosure   = $this->extensionsClosure;
             /** @var SupportedExtensionsInterface $supportedExtensions */
             $supportedExtensions = $extensionsClosure();
diff --git a/src/Parameters/Parameters.php b/src/Parameters/Parameters.php
index 9db7942b..8622a2aa 100644
--- a/src/Parameters/Parameters.php
+++ b/src/Parameters/Parameters.php
@@ -133,4 +133,17 @@ public function getUnrecognizedParameters()
     {
         return $this->unrecognizedParams;
     }
+
+    /**
+     * Returns true if inclusion, field set, sorting, paging, filtering and unrecognized parameters are empty.
+     *
+     * @return bool
+     */
+    public function isEmpty()
+    {
+        return
+            empty($this->getFieldSets()) === true && empty($this->getIncludePaths()) === true &&
+            empty($this->getSortParameters()) === true && empty($this->getPaginationParameters()) === true &&
+            empty($this->getFilteringParameters()) === true && empty($this->getUnrecognizedParameters()) === true;
+    }
 }
diff --git a/src/Parameters/ParametersParser.php b/src/Parameters/ParametersParser.php
index 55ef5d5a..55c3a72b 100644
--- a/src/Parameters/ParametersParser.php
+++ b/src/Parameters/ParametersParser.php
@@ -32,21 +32,6 @@
  */
 class ParametersParser implements ParametersParserInterface
 {
-    /** Parameter name */
-    const PARAM_INCLUDE = 'include';
-
-    /** Parameter name */
-    const PARAM_FIELDS = 'fields';
-
-    /** Parameter name */
-    const PARAM_PAGE = 'page';
-
-    /** Parameter name */
-    const PARAM_FILTER = 'filter';
-
-    /** Parameter name */
-    const PARAM_SORT = 'sort';
-
     /**
      * @var ParametersFactoryInterface
      */
@@ -76,8 +61,9 @@ public function parse(CurrentRequestInterface $request, ExceptionThrowerInterfac
         $contentTypeHeader = null;
 
         try {
+            $contentType = $request->getHeader(HeaderInterface::HEADER_CONTENT_TYPE);
             $contentTypeHeader = Header::parse(
-                $request->getHeader(HeaderInterface::HEADER_CONTENT_TYPE),
+                empty($contentType) === true ? MediaTypeInterface::JSON_API_MEDIA_TYPE : $contentType,
                 HeaderInterface::HEADER_CONTENT_TYPE
             );
         } catch (InvalidArgumentException $exception) {
diff --git a/src/Parameters/RestrictiveParameterChecker.php b/src/Parameters/RestrictiveParameterChecker.php
index ac1589a1..865b18b5 100644
--- a/src/Parameters/RestrictiveParameterChecker.php
+++ b/src/Parameters/RestrictiveParameterChecker.php
@@ -20,7 +20,6 @@
 use \Neomerx\JsonApi\Contracts\Parameters\ParametersInterface;
 use \Neomerx\JsonApi\Contracts\Parameters\SortParameterInterface;
 use \Neomerx\JsonApi\Contracts\Parameters\ParameterCheckerInterface;
-use \Neomerx\JsonApi\Contracts\Parameters\Headers\MediaTypeInterface;
 use \Neomerx\JsonApi\Contracts\Integration\ExceptionThrowerInterface;
 
 /**
@@ -68,11 +67,6 @@ class RestrictiveParameterChecker implements ParameterCheckerInterface
      */
     private $filteringParameters;
 
-    /**
-     * @var bool
-     */
-    private $allowExtensionsSupport;
-
     /**
      * @param ExceptionThrowerInterface $exceptionThrower
      * @param CodecMatcherInterface     $codecMatcher
@@ -82,7 +76,6 @@ class RestrictiveParameterChecker implements ParameterCheckerInterface
      * @param array|null                $sortParameters
      * @param array|null                $pagingParameters
      * @param array|null                $filteringParameters
-     * @param bool                      $allowExtSupport If JSON API extensions support is allowed.
      */
     public function __construct(
         ExceptionThrowerInterface $exceptionThrower,
@@ -92,18 +85,16 @@ public function __construct(
         array $fieldSetTypes = null,
         array $sortParameters = null,
         array $pagingParameters = null,
-        array $filteringParameters = null,
-        $allowExtSupport = false
+        array $filteringParameters = null
     ) {
-        $this->exceptionThrower       = $exceptionThrower;
-        $this->codecMatcher           = $codecMatcher;
-        $this->includePaths           = $includePaths;
-        $this->allowUnrecognized      = $allowUnrecognized;
-        $this->fieldSetTypes          = $fieldSetTypes;
-        $this->sortParameters         = $this->flip($sortParameters);
-        $this->pagingParameters       = $this->flip($pagingParameters);
-        $this->filteringParameters    = $this->flip($filteringParameters);
-        $this->allowExtensionsSupport = $allowExtSupport;
+        $this->exceptionThrower    = $exceptionThrower;
+        $this->codecMatcher        = $codecMatcher;
+        $this->includePaths        = $includePaths;
+        $this->allowUnrecognized   = $allowUnrecognized;
+        $this->fieldSetTypes       = $fieldSetTypes;
+        $this->sortParameters      = $this->flip($sortParameters);
+        $this->pagingParameters    = $this->flip($pagingParameters);
+        $this->filteringParameters = $this->flip($filteringParameters);
     }
 
     /**
@@ -132,16 +123,13 @@ protected function checkAcceptHeader(ParametersInterface $parameters)
     {
         $this->codecMatcher->matchEncoder($parameters->getAcceptHeader());
 
-        // From spec: Servers MUST return a 406 Not Acceptable status code if
-        // the application/vnd.api+json media type is modified by the ext parameter
-        // in the Accept header of a request.
+        // From spec: Servers MUST respond with a 406 Not Acceptable status code
+        // if a request's Accept header contains the JSON API media type and all
+        // instances of that media type are modified with media type parameters.
 
-        // We return 406 if no match found for encoder or
-        // if 'allowExtensionsSupport' set to false (and match found) we check 'ext' **parameter** to be not set.
-        // Thus it can be configured whether we support extensions or not.
-
-        $inputMediaType = $this->codecMatcher->getEncoderHeaderMatchedType();
-        if ($this->isBadMediaType($inputMediaType)) {
+        // We return 406 if no match found for encoder (media type with or wo parameters)
+        // If no encoders were configured for media types with parameters we return 406 anyway
+        if ($this->codecMatcher->getEncoderHeaderMatchedType() === null) {
             $this->exceptionThrower->throwNotAcceptable();
         }
     }
@@ -160,16 +148,13 @@ protected function checkContentTypeHeader(ParametersInterface $parameters)
 
         $this->codecMatcher->findDecoder($parameters->getContentTypeHeader());
 
-        // From spec: servers MUST return a 415 Unsupported Media Type status code if
-        // the application/vnd.api+json media type is modified by the ext parameter
-        // in the Content-Type header of a request.
-
-        // We return 415 if no match found for decoder or
-        // if 'allowExtensionsSupport' set to false (and match found) we check 'ext' **parameter** to be not set.
-        // Thus it can be configured whether we support extensions or not.
+        // From spec: Servers MUST respond with a 415 Unsupported Media Type status code
+        // if a request specifies the header Content-Type: application/vnd.api+json with
+        // any media type parameters.
 
-        $inputMediaType = $this->codecMatcher->getDecoderHeaderMatchedType();
-        if ($this->isBadMediaType($inputMediaType)) {
+        // We return 415 if no match found for decoder (media type with or wo parameters)
+        // If no decoders were configured for media types with parameters we return 415 anyway
+        if ($this->codecMatcher->getDecoderHeaderMatchedType() === null) {
             $this->exceptionThrower->throwUnsupportedMediaType();
         }
     }
@@ -266,20 +251,6 @@ private function flip(array $array = null)
         return $array === null ? null : array_flip($array);
     }
 
-    /**
-     * @param MediaTypeInterface|null $mediaType
-     *
-     * @return bool
-     */
-    private function isBadMediaType(MediaTypeInterface $mediaType = null)
-    {
-        return $mediaType === null || (
-            $this->allowExtensionsSupport === false &&
-            $mediaType->getMediaType() === MediaTypeInterface::JSON_API_MEDIA_TYPE &&
-            $mediaType->getParameters() !== null &&
-            array_key_exists(MediaTypeInterface::PARAM_EXT, $mediaType->getParameters()) === true);
-    }
-
     /**
      * Check input fields against allowed.
      *
diff --git a/src/Schema/Link.php b/src/Schema/Link.php
index 743d0ff1..c9eb9837 100644
--- a/src/Schema/Link.php
+++ b/src/Schema/Link.php
@@ -33,16 +33,25 @@ class Link implements LinkInterface
      */
     private $meta;
 
+    /**
+     * @var bool
+     */
+    private $treatAsHref;
+
     /**
      * @param string            $subHref
      * @param array|object|null $meta
+     * @param bool              $treatAsHref If $subHref is a full URL and must not be concatenated with other URLs.
      */
-    public function __construct($subHref, $meta = null)
+    public function __construct($subHref, $meta = null, $treatAsHref = false)
     {
-        assert('is_string($subHref) && (is_null($meta) || is_object($meta) || is_array($meta))');
+        assert(
+            'is_string($subHref) && (is_null($meta) || is_object($meta) || is_array($meta)) && is_bool($treatAsHref)'
+        );
 
-        $this->subHref = $subHref;
-        $this->meta    = $meta;
+        $this->subHref     = $subHref;
+        $this->meta        = $meta;
+        $this->treatAsHref = $treatAsHref;
     }
 
     /**
@@ -60,4 +69,12 @@ public function getMeta()
     {
         return $this->meta;
     }
+
+    /**
+     * @inheritdoc
+     */
+    public function isTreatAsHref()
+    {
+        return $this->treatAsHref;
+    }
 }
diff --git a/src/Schema/RelationshipObject.php b/src/Schema/RelationshipObject.php
index 47b5575f..92e814a8 100644
--- a/src/Schema/RelationshipObject.php
+++ b/src/Schema/RelationshipObject.php
@@ -16,7 +16,6 @@
  * limitations under the License.
  */
 
-use \Neomerx\JsonApi\Contracts\Schema\LinkInterface;
 use \Neomerx\JsonApi\Contracts\Schema\RelationshipObjectInterface;
 
 /**
@@ -35,19 +34,14 @@ class RelationshipObject implements RelationshipObjectInterface
     private $data;
 
     /**
-     * @var bool
+     * @var array<string,\Neomerx\JsonApi\Contracts\Schema\LinkInterface>
      */
-    private $isShowAsReference;
+    private $links;
 
     /**
-     * @var bool
+     * @var mixed
      */
-    private $isShowSelf;
-
-    /**
-     * @var bool
-     */
-    private $isShowRelated;
+    private $meta;
 
     /**
      * @var bool
@@ -55,79 +49,28 @@ class RelationshipObject implements RelationshipObjectInterface
     private $isShowData;
 
     /**
-     * @var LinkInterface
-     */
-    private $selfLink;
-
-    /**
-     * @var LinkInterface
-     */
-    private $relatedLink;
-
-    /**
-     * @var bool
-     */
-    private $isShowMeta;
-
-    /**
-     * @var bool
-     */
-    private $isShowPagination;
-
-    /**
-     * @var LinkInterface[]|null
-     */
-    private $pagination;
-
-    /**
-     * @param string               $name
-     * @param object|array|null    $data
-     * @param LinkInterface        $selfLink
-     * @param LinkInterface        $relatedLink
-     * @param bool                 $isShowAsRef
-     * @param bool                 $isShowSelf
-     * @param bool                 $isShowRelated
-     * @param bool                 $isShowData
-     * @param bool                 $isShowMeta
-     * @param bool                 $isShowPagination
-     * @param LinkInterface[]|null $pagination
+     * @param string                                                        $name
+     * @param object|array|null                                             $data
+     * @param array<string,\Neomerx\JsonApi\Contracts\Schema\LinkInterface> $links
+     * @param mixed                                                         $meta
+     * @param bool                                                          $isShowData
      */
     public function __construct(
         $name,
         $data,
-        LinkInterface $selfLink,
-        LinkInterface $relatedLink,
-        $isShowAsRef,
-        $isShowSelf,
-        $isShowRelated,
-        $isShowData,
-        $isShowMeta,
-        $isShowPagination,
-        $pagination
+        $links,
+        $meta,
+        $isShowData
     ) {
         assert(
-            'is_string($name) &&'.
-            '(is_object($data) || is_array($data) || is_null($data)) &&'.
-            'is_bool($isShowAsRef) && is_bool($isShowSelf) && is_bool($isShowRelated) && is_bool($isShowMeta) &&'.
-            'is_bool($isShowPagination) &&'.
-            '(is_null($pagination) || is_array($pagination))'
-        );
-        assert(
-            '$isShowSelf || $isShowRelated || $isShowData || $isShowMeta',
-            'Specification requires at least one of them to be shown'
+            'is_string($name) && (is_object($data) || is_array($data) || is_null($data)) && is_array($links)'
         );
 
-        $this->name              = $name;
-        $this->data              = $data;
-        $this->selfLink          = $selfLink;
-        $this->relatedLink       = $relatedLink;
-        $this->isShowAsReference = $isShowAsRef;
-        $this->isShowSelf        = $isShowSelf;
-        $this->isShowRelated     = $isShowRelated;
-        $this->isShowData        = $isShowData;
-        $this->isShowMeta        = $isShowMeta;
-        $this->isShowPagination  = $isShowPagination;
-        $this->pagination        = $pagination;
+        $this->name       = $name;
+        $this->data       = $data;
+        $this->links      = $links;
+        $this->meta       = $meta;
+        $this->isShowData = $isShowData;
     }
 
     /**
@@ -141,33 +84,25 @@ public function getName()
     /**
      * @inheritdoc
      */
-    public function isShowSelf()
-    {
-        return $this->isShowSelf;
-    }
-
-    /**
-     * @inheritdoc
-     */
-    public function getSelfLink()
+    public function getData()
     {
-        return $this->selfLink;
+        return $this->data;
     }
 
     /**
      * @inheritdoc
      */
-    public function isShowAsReference()
+    public function getLinks()
     {
-        return $this->isShowAsReference;
+        return $this->links;
     }
 
     /**
      * @inheritdoc
      */
-    public function isShowRelated()
+    public function getMeta()
     {
-        return $this->isShowRelated;
+        return $this->meta;
     }
 
     /**
@@ -177,44 +112,4 @@ public function isShowData()
     {
         return $this->isShowData;
     }
-
-    /**
-     * @inheritdoc
-     */
-    public function getRelatedLink()
-    {
-        return $this->relatedLink;
-    }
-
-    /**
-     * @inheritdoc
-     */
-    public function isShowMeta()
-    {
-        return $this->isShowMeta;
-    }
-
-    /**
-     * @inheritdoc
-     */
-    public function isShowPagination()
-    {
-        return $this->isShowPagination;
-    }
-
-    /**
-     * @inheritdoc
-     */
-    public function getData()
-    {
-        return $this->data;
-    }
-
-    /**
-     * @inheritdoc
-     */
-    public function getPagination()
-    {
-        return $this->pagination;
-    }
 }
diff --git a/src/Schema/ResourceObject.php b/src/Schema/ResourceObject.php
index dee784ed..3ce9551a 100644
--- a/src/Schema/ResourceObject.php
+++ b/src/Schema/ResourceObject.php
@@ -16,18 +16,15 @@
  * limitations under the License.
  */
 
+use \Neomerx\JsonApi\Contracts\Schema\LinkInterface;
 use \Neomerx\JsonApi\Contracts\Schema\ResourceObjectInterface;
+use \Neomerx\JsonApi\Contracts\Schema\SchemaProviderInterface;
 
 /**
  * @package Neomerx\JsonApi
  */
 class ResourceObject implements ResourceObjectInterface
 {
-    /**
-     * @var string
-     */
-    private $type;
-
     /**
      * @var string
      */
@@ -41,94 +38,104 @@ class ResourceObject implements ResourceObjectInterface
     /**
      * @var mixed
      */
-    private $meta;
+    private $primaryMeta;
 
     /**
      * @var bool
      */
-    private $isShowSelf;
+    private $isPrimaryMetaSet = false;
 
     /**
-     * @var string
+     * @var LinkInterface
      */
-    private $selfUrl;
+    private $selfSubLink;
 
     /**
      * @var bool
      */
-    private $isShowMeta;
+    private $isInArray;
+
+    /**
+     * @var SchemaProviderInterface
+     */
+    private $schema;
+
+    /**
+     * @var object
+     */
+    private $resource;
+
+    /**
+     * @var array<string,int>|null
+     */
+    private $attributeKeysFilter;
 
     /**
      * @var bool
      */
-    private $isShowSelfInIncluded;
+    private $isSelfSubLinkSet = false;
 
     /**
      * @var bool
      */
-    private $isShowRelationshipsInIncluded;
+    private $isRelationshipMetaSet = false;
+
+    /**
+     * @var mixed
+     */
+    private $relationshipMeta;
 
     /**
      * @var bool
      */
-    private $isShowMetaInIncluded;
+    private $isInclusionMetaSet = false;
+
+    /**
+     * @var mixed
+     */
+    private $inclusionMeta;
 
     /**
      * @var bool
      */
-    private $isShowMetaInRlShips;
+    private $isRelPrimaryMetaSet = false;
+
+    /**
+     * @var mixed
+     */
+    private $relPrimaryMeta;
 
     /**
      * @var bool
      */
-    private $isInArray;
+    private $isRelIncMetaSet = false;
 
     /**
-     * @param bool   $isInArray
-     * @param string $type
-     * @param string $idx
-     * @param array  $attributes
-     * @param mixed  $meta
-     * @param string $selfUrl
-     * @param bool   $isShowSelf
-     * @param bool   $isShowMeta
-     * @param bool   $isShowSelfInIncluded
-     * @param bool   $isShowRelShipsInIncluded
-     * @param bool   $isShowMetaInIncluded
-     * @param bool   $isShowMetaInRlShips
+     * @var mixed
+     */
+    private $relInclusionMeta;
+
+    /**
+     * @param SchemaProviderInterface $schema
+     * @param object                  $resource
+     * @param bool                    $isInArray
+     * @param array<string,int>|null  $attributeKeysFilter
      */
     public function __construct(
+        SchemaProviderInterface $schema,
+        $resource,
         $isInArray,
-        $type,
-        $idx,
-        array $attributes,
-        $meta,
-        $selfUrl,
-        $isShowSelf,
-        $isShowMeta,
-        $isShowSelfInIncluded,
-        $isShowRelShipsInIncluded,
-        $isShowMetaInIncluded,
-        $isShowMetaInRlShips
+        array $attributeKeysFilter = null
     ) {
         assert(
-            'is_bool($isInArray) && is_string($type) && is_string($idx) && is_array($attributes) &&'.
-            'is_string($selfUrl) && is_bool($isShowSelf) && is_bool($isShowMeta) && is_bool($isShowMetaInRlShips) &&'.
-            'is_bool($isShowSelfInIncluded) && is_bool($isShowRelShipsInIncluded) && is_bool($isShowMetaInIncluded)'
+            'is_bool($isInArray) && is_object($resource) && '.
+            '($attributeKeysFilter === null || is_array($attributeKeysFilter))'
         );
 
-        $this->isInArray                     = $isInArray;
-        $this->type                          = $type;
-        $this->idx                           = $idx;
-        $this->attributes                    = $attributes;
-        $this->meta                          = $meta;
-        $this->isShowSelf                    = $isShowSelf;
-        $this->selfUrl                       = $selfUrl;
-        $this->isShowMeta                    = $isShowMeta;
-        $this->isShowSelfInIncluded          = $isShowSelfInIncluded;
-        $this->isShowRelationshipsInIncluded = $isShowRelShipsInIncluded;
-        $this->isShowMetaInIncluded          = $isShowMetaInIncluded;
-        $this->isShowMetaInRlShips           = $isShowMetaInRlShips;
+        $this->schema              = $schema;
+        $this->resource            = $resource;
+        $this->isInArray           = $isInArray;
+        $this->attributeKeysFilter = $attributeKeysFilter;
     }
 
     /**
@@ -136,7 +143,7 @@ public function __construct(
      */
     public function getType()
     {
-        return $this->type;
+        return $this->schema->getResourceType();
     }
 
     /**
@@ -144,7 +151,7 @@ public function getType()
      */
     public function getId()
     {
-        return $this->idx;
+        return $this->idx === null ? $this->idx = (string)$this->schema->getId($this->resource) : $this->idx;
     }
 
     /**
@@ -152,78 +159,132 @@ public function getId()
      */
     public function getAttributes()
     {
+        if ($this->attributes === null) {
+            $attributes = $this->schema->getAttributes($this->resource);
+            if ($this->attributeKeysFilter !== null) {
+                $attributes = array_intersect_key($attributes, $this->attributeKeysFilter);
+            }
+            $this->attributes = $attributes;
+        }
+
         return $this->attributes;
     }
 
     /**
      * @inheritdoc
      */
-    public function getMeta()
+    public function getSelfSubLink()
     {
-        return $this->meta;
+        if ($this->isSelfSubLinkSet === false) {
+            $this->selfSubLink      = $this->schema->getSelfSubLink($this->resource);
+            $this->isSelfSubLinkSet = true;
+        }
+
+        return $this->selfSubLink;
     }
 
     /**
      * @inheritdoc
      */
-    public function getSelfUrl()
+    public function isShowSelf()
     {
-        return $this->selfUrl;
+        return $this->schema->isShowSelf();
     }
 
     /**
      * @inheritdoc
      */
-    public function isShowSelf()
+    public function isShowSelfInIncluded()
     {
-        return $this->isShowSelf;
+        return $this->schema->isShowSelfInIncluded();
     }
 
     /**
      * @inheritdoc
      */
-    public function isShowMeta()
+    public function isShowAttributesInIncluded()
     {
-        return $this->isShowMeta;
+        return $this->schema->isShowAttributesInIncluded();
     }
 
     /**
      * @inheritdoc
      */
-    public function isShowSelfInIncluded()
+    public function isShowRelationshipsInIncluded()
     {
-        return $this->isShowSelfInIncluded;
+        return $this->schema->isShowRelationshipsInIncluded();
     }
 
     /**
      * @inheritdoc
      */
-    public function isShowMetaInIncluded()
+    public function isInArray()
     {
-        return $this->isShowMetaInIncluded;
+        return $this->isInArray;
     }
 
     /**
      * @inheritdoc
      */
-    public function isShowMetaInRelationships()
+    public function getPrimaryMeta()
     {
-        return $this->isShowMetaInRlShips;
+        if ($this->isPrimaryMetaSet === false) {
+            $this->primaryMeta = $this->schema->getPrimaryMeta($this->resource);
+            $this->isPrimaryMetaSet = true;
+        }
+
+        return $this->primaryMeta;
     }
 
     /**
      * @inheritdoc
      */
-    public function isShowRelationshipsInIncluded()
+    public function getInclusionMeta()
     {
-        return $this->isShowRelationshipsInIncluded;
+        if ($this->isInclusionMetaSet === false) {
+            $this->inclusionMeta = $this->schema->getInclusionMeta($this->resource);
+            $this->isInclusionMetaSet = true;
+        }
+
+        return $this->inclusionMeta;
     }
 
     /**
      * @inheritdoc
      */
-    public function isInArray()
+    public function getRelationshipsPrimaryMeta()
     {
-        return $this->isInArray;
+        if ($this->isRelPrimaryMetaSet === false) {
+            $this->relPrimaryMeta = $this->schema->getRelationshipsPrimaryMeta($this->resource);
+            $this->isRelPrimaryMetaSet = true;
+        }
+
+        return $this->relPrimaryMeta;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getRelationshipsInclusionMeta()
+    {
+        if ($this->isRelIncMetaSet === false) {
+            $this->relInclusionMeta = $this->schema->getRelationshipsInclusionMeta($this->resource);
+            $this->isRelIncMetaSet = true;
+        }
+
+        return $this->relInclusionMeta;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getLinkageMeta()
+    {
+        if ($this->isRelationshipMetaSet === false) {
+            $this->relationshipMeta = $this->schema->getLinkageMeta($this->resource);
+            $this->isRelationshipMetaSet = true;
+        }
+
+        return $this->relationshipMeta;
     }
 }
diff --git a/src/Schema/SchemaFactory.php b/src/Schema/SchemaFactory.php
index 98a1abc7..238374c3 100644
--- a/src/Schema/SchemaFactory.php
+++ b/src/Schema/SchemaFactory.php
@@ -16,8 +16,8 @@
  * limitations under the License.
  */
 
-use \Neomerx\JsonApi\Contracts\Schema\LinkInterface;
 use \Neomerx\JsonApi\Contracts\Schema\SchemaFactoryInterface;
+use \Neomerx\JsonApi\Contracts\Schema\SchemaProviderInterface;
 
 /**
  * @package Neomerx\JsonApi
@@ -36,64 +36,20 @@ public function createContainer(array $providers = [])
      * @inheritdoc
      */
     public function createResourceObject(
+        SchemaProviderInterface $schema,
+        $resource,
         $isInArray,
-        $type,
-        $idx,
-        array $attributes,
-        $meta,
-        $selfUrl,
-        $isShowSelf,
-        $isShowMeta,
-        $isShowSelfInIncluded,
-        $isShowRelShipsInIncluded,
-        $isShowMetaInIncluded,
-        $isShowMetaInRlShips
+        $attributeKeysFilter = null
     ) {
-        return new ResourceObject(
-            $isInArray,
-            $type,
-            $idx,
-            $attributes,
-            $meta,
-            $selfUrl,
-            $isShowSelf,
-            $isShowMeta,
-            $isShowSelfInIncluded,
-            $isShowRelShipsInIncluded,
-            $isShowMetaInIncluded,
-            $isShowMetaInRlShips
-        );
+        return new ResourceObject($schema, $resource, $isInArray, $attributeKeysFilter);
     }
 
     /**
      * @inheritdoc
      */
-    public function createRelationshipObject(
-        $name,
-        $data,
-        LinkInterface $selfLink,
-        LinkInterface $relatedLink,
-        $isShowAsRef,
-        $isShowSelf,
-        $isShowRelated,
-        $isShowData,
-        $isShowMeta,
-        $isShowPagination,
-        $pagination
-    ) {
-        return new RelationshipObject(
-            $name,
-            $data,
-            $selfLink,
-            $relatedLink,
-            $isShowAsRef,
-            $isShowSelf,
-            $isShowRelated,
-            $isShowData,
-            $isShowMeta,
-            $isShowPagination,
-            $pagination
-        );
+    public function createRelationshipObject($name, $data, $links, $meta, $isShowData)
+    {
+        return new RelationshipObject($name, $data, $links, $meta, $isShowData);
     }
 
     /**
diff --git a/src/Schema/SchemaProvider.php b/src/Schema/SchemaProvider.php
index 40344579..920a223a 100644
--- a/src/Schema/SchemaProvider.php
+++ b/src/Schema/SchemaProvider.php
@@ -19,6 +19,7 @@
 use \Closure;
 use \Neomerx\JsonApi\Contracts\Schema\LinkInterface;
 use \Neomerx\JsonApi\Contracts\Schema\ContainerInterface;
+use \Neomerx\JsonApi\Contracts\Document\DocumentInterface;
 use \Neomerx\JsonApi\Contracts\Schema\SchemaFactoryInterface;
 use \Neomerx\JsonApi\Contracts\Schema\SchemaProviderInterface;
 
@@ -27,35 +28,32 @@
  */
 abstract class SchemaProvider implements SchemaProviderInterface
 {
-    /** Linked data key. */
-    const DATA = 'data';
+    /** Links information */
+    const LINKS = DocumentInterface::KEYWORD_LINKS;
 
-    /** If link should be shown as reference. */
-    const SHOW_AS_REF = 'asRef';
+    /** Linked data key. */
+    const DATA = DocumentInterface::KEYWORD_DATA;
 
-    /** If meta information should be shown. */
-    const SHOW_META = 'showMeta';
+    /** Relationship meta */
+    const META = DocumentInterface::KEYWORD_META;
 
-    /** If 'self' URL should be shown. Requires 'self' controller to be set. */
+    /** If 'self' URL should be shown. */
     const SHOW_SELF = 'showSelf';
 
-    /** If 'related' URL should be shown. Requires 'related' controller to be set. */
+    /** If 'related' URL should be shown. */
     const SHOW_RELATED = 'related';
 
     /** If data should be shown in relationships. */
-    const SHOW_DATA_IN_RELATIONSHIPS = 'showDataInRelationships';
-
-    /** If link pagination information should be shown. */
-    const SHOW_PAGINATION = 'showPagination';
+    const SHOW_DATA = 'showData';
 
-    /** Link pagination information */
-    const PAGINATION = 'pagination';
+    /** Property name */
+    const ATTRIBUTES = DocumentInterface::KEYWORD_ATTRIBUTES;
 
-    /** Default 'self' sub-URL could be changed with this key */
-    const SELF_SUB_URL = 'selfSubUrl';
+    /** Property name */
+    const RELATIONSHIPS = DocumentInterface::KEYWORD_RELATIONSHIPS;
 
-    /** Default 'related' sub-URL could be changed with this key */
-    const RELATED_SUB_URL = 'relatedSubUrl';
+    /** Property name */
+    const INCLUDED = DocumentInterface::KEYWORD_INCLUDED;
 
     /**
      * @var string
@@ -65,23 +63,13 @@ abstract class SchemaProvider implements SchemaProviderInterface
     /**
      * @var string
      */
-    protected $baseSelfUrl;
+    protected $selfSubUrl;
 
     /**
      * @var bool
      */
     protected $isShowSelf = true;
 
-    /**
-     * @var bool
-     */
-    protected $isShowMeta = false;
-
-    /**
-     * @var bool
-     */
-    protected $isShowMetaInRelationships = false;
-
     /**
      * @var bool
      */
@@ -90,12 +78,12 @@ abstract class SchemaProvider implements SchemaProviderInterface
     /**
      * @var bool
      */
-    protected $isShowRelShipsInIncluded = false;
+    protected $isShowAttributesInIncluded = true;
 
     /**
      * @var bool
      */
-    protected $isShowMetaInIncluded = false;
+    protected $isShowRelShipsInIncluded = false;
 
     /**
      * @var SchemaFactoryInterface
@@ -113,14 +101,10 @@ abstract class SchemaProvider implements SchemaProviderInterface
      */
     public function __construct(SchemaFactoryInterface $factory, ContainerInterface $container)
     {
-        assert('is_string($this->resourceType) && empty($this->resourceType) === false', 'Resource type not set.');
-        assert(
-            'is_bool($this->isShowSelfInIncluded) &&'.
-            'is_bool($this->isShowRelShipsInIncluded) &&'.
-            'is_bool($this->isShowMetaInIncluded)'
-        );
-
-        assert('is_string($this->baseSelfUrl) && empty($this->baseSelfUrl) === false', 'Base \'self\' not set.');
+        assert('is_string($this->resourceType) && empty($this->resourceType) === false', 'Resource type not set');
+        assert('is_bool($this->isShowSelfInIncluded) && is_bool($this->isShowRelShipsInIncluded)');
+        assert('is_string($this->selfSubUrl) && empty($this->selfSubUrl) === false', '\'self\' sub-URL not set');
+        assert('substr($this->selfSubUrl, -1) === \'/\'', 'Sub-url should end with \'/\' separator');
 
         $this->factory   = $factory;
         $this->container = $container;
@@ -137,15 +121,15 @@ public function getResourceType()
     /**
      * @inheritdoc
      */
-    public function getSelfUrl($resource)
+    public function getSelfSubLink($resource)
     {
-        return $this->getBaseSelfUrl($resource).$this->getId($resource);
+        return new Link($this->selfSubUrl . $this->getId($resource));
     }
 
     /**
      * @inheritdoc
      */
-    public function getMeta($resource)
+    public function getPrimaryMeta($resource)
     {
         return null;
     }
@@ -153,49 +137,65 @@ public function getMeta($resource)
     /**
      * @inheritdoc
      */
-    public function isShowSelf()
+    public function getLinkageMeta($resource)
     {
-        return $this->isShowSelf;
+        return null;
     }
 
     /**
      * @inheritdoc
      */
-    public function isShowMeta()
+    public function getInclusionMeta($resource)
     {
-        return $this->isShowMeta;
+        return null;
     }
 
     /**
      * @inheritdoc
      */
-    public function isShowSelfInIncluded()
+    public function getRelationshipsPrimaryMeta($resource)
     {
-        return $this->isShowSelfInIncluded;
+        return null;
     }
 
     /**
      * @inheritdoc
      */
-    public function isShowRelationshipsInIncluded()
+    public function getRelationshipsInclusionMeta($resource)
     {
-        return $this->isShowRelShipsInIncluded;
+        return null;
     }
 
     /**
      * @inheritdoc
      */
-    public function isShowMetaInIncluded()
+    public function isShowSelf()
     {
-        return $this->isShowMetaInIncluded;
+        return $this->isShowSelf;
     }
 
     /**
      * @inheritdoc
      */
-    public function isShowMetaInRelationships()
+    public function isShowSelfInIncluded()
     {
-        return $this->isShowMetaInRelationships;
+        return $this->isShowSelfInIncluded;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function isShowAttributesInIncluded()
+    {
+        return $this->isShowAttributesInIncluded;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function isShowRelationshipsInIncluded()
+    {
+        return $this->isShowRelShipsInIncluded;
     }
 
     /**
@@ -214,60 +214,26 @@ public function getRelationships($resource)
     /**
      * @inheritdoc
      */
-    public function getRelationshipObjectIterator($resource)
+    public function createResourceObject($resource, $isOriginallyArrayed, $attributeKeysFilter = null)
     {
-        foreach ($this->getRelationships($resource) as $name => $desc) {
-            $data          = $this->readData($desc);
-            $isShowMeta    = ($this->getValue($desc, self::SHOW_META, false) === true);
-            $isShowSelf    = ($this->getValue($desc, self::SHOW_SELF, false) === true);
-            $isShowAsRef   = ($this->getValue($desc, self::SHOW_AS_REF, false) === true);
-            $isShowRelated = ($this->getValue($desc, self::SHOW_RELATED, false) === true);
-            $isShowLinkage = ($this->getValue($desc, self::SHOW_DATA_IN_RELATIONSHIPS, true) === true);
-
-            list($isShowPagination, $pagination) = $this->readPagination($desc);
-
-            $selfLink    = $this->getSelfLink($name, $desc, $data);
-            $relatedLink = $this->getRelatedLink($name, $desc, $data);
-
-            yield $this->factory->createRelationshipObject(
-                $name,
-                $data,
-                $selfLink,
-                $relatedLink,
-                $isShowAsRef,
-                $isShowSelf,
-                $isShowRelated,
-                $isShowLinkage,
-                $isShowMeta,
-                $isShowPagination,
-                $pagination
-            );
-        }
+        return $this->factory->createResourceObject($this, $resource, $isOriginallyArrayed, $attributeKeysFilter);
     }
 
     /**
      * @inheritdoc
      */
-    public function createResourceObject($resource, $isOriginallyArrayed, array $attributeKeysFilter = null)
+    public function getRelationshipObjectIterator($resource)
     {
-        $attributes = $this->getAttributes($resource);
-        if ($attributeKeysFilter !== null) {
-            $attributes = array_intersect_key($attributes, $attributeKeysFilter);
+        foreach ($this->getRelationships($resource) as $name => $desc) {
+            $data          = $this->readData($desc);
+            $meta          = $this->getValue($desc, self::META, null);
+            $isShowSelf    = ($this->getValue($desc, self::SHOW_SELF, false) === true);
+            $isShowRelated = ($this->getValue($desc, self::SHOW_RELATED, false) === true);
+            $isShowData    = ($this->getValue($desc, self::SHOW_DATA, true) === true);
+            $links         = $this->readLinks($name, $desc, $isShowSelf, $isShowRelated);
+
+            yield $this->factory->createRelationshipObject($name, $data, $links, $meta, $isShowData);
         }
-        return $this->factory->createResourceObject(
-            $isOriginallyArrayed,
-            $this->getResourceType(),
-            (string)$this->getId($resource),
-            $attributes,
-            $this->getMeta($resource),
-            $this->getSelfUrl($resource),
-            $this->isShowSelf(),
-            $this->isShowMeta(),
-            $this->isShowSelfInIncluded(),
-            $this->isShowRelationshipsInIncluded(),
-            $this->isShowMetaInIncluded(),
-            $this->isShowMetaInRelationships()
-        );
     }
 
     /**
@@ -279,53 +245,24 @@ public function getIncludePaths()
     }
 
     /**
-     * Get the base self URL
-     *
-     * @param object $resource
-     *
-     * @return string
-     */
-    protected function getBaseSelfUrl($resource)
-    {
-        $resource ?: null;
-
-        substr($this->baseSelfUrl, -1) === '/' ?: $this->baseSelfUrl .= '/';
-
-        return $this->baseSelfUrl;
-    }
-
-    /**
-     * Get link for 'self' relationship url.
-     *
-     * @param string            $relationshipName
-     * @param array             $description
-     * @param mixed             $relationshipData
-     * @param null|array|object $meta
+     * @param string $relationshipName
+     * @param array  $description
+     * @param bool   $isShowSelf
+     * @param bool   $isShowRelated
      *
-     * @return LinkInterface
+     * @return array <string,LinkInterface>
      */
-    protected function getSelfLink($relationshipName, array $description, $relationshipData, $meta = null)
+    protected function readLinks($relationshipName, array $description, $isShowSelf, $isShowRelated)
     {
-        $relationshipData ?: null;
-        $subHref = $this->getValue($description, self::SELF_SUB_URL, '/relationships/'.$relationshipName);
-        return $this->factory->createLink($subHref, $meta);
-    }
+        $links = $this->getValue($description, self::LINKS, []);
+        if ($isShowSelf === true && isset($links[LinkInterface::SELF]) === false) {
+            $links[LinkInterface::SELF] = $this->factory->createLink('relationships/'.$relationshipName);
+        }
+        if ($isShowRelated === true && isset($links[LinkInterface::RELATED]) === false) {
+            $links[LinkInterface::RELATED] = $this->factory->createLink($relationshipName);
+        }
 
-    /**
-     * Get link for 'self' relationship url.
-     *
-     * @param string            $relationshipName
-     * @param array             $description
-     * @param mixed             $relationshipData
-     * @param null|array|object $meta
-     *
-     * @return LinkInterface
-     */
-    protected function getRelatedLink($relationshipName, array $description, $relationshipData, $meta = null)
-    {
-        $relationshipData ?: null;
-        $href = $this->getValue($description, self::RELATED_SUB_URL, '/'.$relationshipName);
-        return $this->factory->createLink($href, $meta);
+        return $links;
     }
 
     /**
@@ -353,26 +290,4 @@ private function readData(array $description)
         }
         return $data;
     }
-
-    /**
-     * @param array $description
-     *
-     * @return array
-     */
-    private function readPagination(array $description)
-    {
-        $pagination       = null;
-        $isShowPagination = $this->getValue($description, self::SHOW_PAGINATION, false);
-
-        if ($isShowPagination === true &&
-            isset($description[self::PAGINATION]) === true &&
-            empty($description[self::PAGINATION]) === false
-        ) {
-            $pagination = $description[self::PAGINATION];
-        }
-
-        $isShowPagination = ($isShowPagination === true && $pagination !== null);
-
-        return [$isShowPagination, $pagination];
-    }
 }
diff --git a/tests/Codec/CodecMatcherTest.php b/tests/Codec/CodecMatcherTest.php
index 89a6346b..bdb32e26 100644
--- a/tests/Codec/CodecMatcherTest.php
+++ b/tests/Codec/CodecMatcherTest.php
@@ -265,6 +265,36 @@ public function testMatchWithZeroQ()
         $this->assertNull($matcher->getEncoderRegisteredMatchedType());
     }
 
+    /**
+     * Test encoder.
+     */
+    public function testSetEncoder()
+    {
+        $matcher = $this->getTestCodecMatcher();
+
+        $foo = 'foo';
+        $matcher->setEncoder(function () use ($foo) {
+            return $foo;
+        });
+
+        $this->assertEquals($foo, $matcher->getEncoder());
+    }
+
+    /**
+     * Test decoder.
+     */
+    public function testSetDecoder()
+    {
+        $matcher = $this->getTestCodecMatcher();
+
+        $foo = 'foo';
+        $matcher->setDecoder(function () use ($foo) {
+            return $foo;
+        });
+
+        $this->assertEquals($foo, $matcher->getDecoder());
+    }
+
     /**
      * @return CodecMatcherInterface
      */
diff --git a/tests/Data/AuthorSchema.php b/tests/Data/AuthorSchema.php
index d2ebb269..52b76bb0 100644
--- a/tests/Data/AuthorSchema.php
+++ b/tests/Data/AuthorSchema.php
@@ -29,7 +29,7 @@ class AuthorSchema extends DevSchemaProvider
     /**
      * @inheritdoc
      */
-    protected $baseSelfUrl = 'http://example.com/people/';
+    protected $selfSubUrl = '/people/';
 
     /**
      * @inheritdoc
diff --git a/tests/Data/CommentSchema.php b/tests/Data/CommentSchema.php
index b6835aee..849cf2c7 100644
--- a/tests/Data/CommentSchema.php
+++ b/tests/Data/CommentSchema.php
@@ -34,7 +34,7 @@ class CommentSchema extends DevSchemaProvider
     /**
      * @inheritdoc
      */
-    protected $baseSelfUrl = 'http://example.com/comments/';
+    protected $selfSubUrl = '/comments/';
 
     /**
      * @inheritdoc
diff --git a/tests/Data/DevSchemaProvider.php b/tests/Data/DevSchemaProvider.php
index c70e503c..77976986 100644
--- a/tests/Data/DevSchemaProvider.php
+++ b/tests/Data/DevSchemaProvider.php
@@ -46,9 +46,33 @@ abstract class DevSchemaProvider extends SchemaProvider
     private $includePaths = [];
 
     /**
-     * @var array
+     * @var mixed
+     */
+    private $relationshipsMeta;
+
+    /**
+     * @inheritdoc
      */
-    private $relationshipMeta;
+    public function getRelationshipsPrimaryMeta($resource)
+    {
+        return $this->relationshipsMeta ?: parent::getRelationshipsPrimaryMeta($resource);
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getRelationshipsInclusionMeta($resource)
+    {
+        return $this->relationshipsMeta ?: parent::getRelationshipsInclusionMeta($resource);
+    }
+
+    /**
+     * @param array $relationshipMeta
+     */
+    public function setRelationshipsMeta($relationshipMeta)
+    {
+        $this->relationshipsMeta = $relationshipMeta;
+    }
 
     /**
      * Add to 'add to link' list.
@@ -112,14 +136,6 @@ public function setIncludePaths($includePaths)
         $this->includePaths = $includePaths;
     }
 
-    /**
-     * @param array $relationshipMeta
-     */
-    public function setRelationshipMeta($relationshipMeta)
-    {
-        $this->relationshipMeta = $relationshipMeta;
-    }
-
     /**
      * Add/remove values in input array.
      *
@@ -139,17 +155,4 @@ protected function fixLinks(array &$links)
             unset($links[$key]);
         }
     }
-
-    /**
-     * @param string $relationshipName
-     * @param array  $description
-     * @param mixed  $relationshipData
-     * @param null   $meta
-     *
-     * @return \Neomerx\JsonApi\Contracts\Schema\LinkInterface
-     */
-    protected function getSelfLink($relationshipName, array $description, $relationshipData, $meta = null)
-    {
-        return parent::getSelfLink($relationshipName, $description, $relationshipData, $this->relationshipMeta);
-    }
 }
diff --git a/tests/Data/DummySchema.php b/tests/Data/DummySchema.php
index 9dcff54a..f1feadc2 100644
--- a/tests/Data/DummySchema.php
+++ b/tests/Data/DummySchema.php
@@ -29,7 +29,7 @@ class DummySchema extends DevSchemaProvider
     /**
      * @inheritdoc
      */
-    protected $baseSelfUrl = 'dummyUrl';
+    protected $selfSubUrl = '/dummyUrl/';
 
     /**
      * @inheritdoc
diff --git a/tests/Data/PostSchema.php b/tests/Data/PostSchema.php
index f44e21fc..a6824a77 100644
--- a/tests/Data/PostSchema.php
+++ b/tests/Data/PostSchema.php
@@ -29,7 +29,7 @@ class PostSchema extends DevSchemaProvider
     /**
      * @inheritdoc
      */
-    protected $baseSelfUrl = 'http://example.com/posts';
+    protected $selfSubUrl = '/posts/';
 
     /**
      * @inheritdoc
diff --git a/tests/Data/SiteSchema.php b/tests/Data/SiteSchema.php
index 0b855bd1..83606c0a 100644
--- a/tests/Data/SiteSchema.php
+++ b/tests/Data/SiteSchema.php
@@ -32,7 +32,7 @@ class SiteSchema extends DevSchemaProvider
     /**
      * @inheritdoc
      */
-    protected $baseSelfUrl = 'http://example.com/sites';
+    protected $selfSubUrl = '/sites/';
 
     /**
      * @param SchemaFactoryInterface $factory
diff --git a/tests/Document/DocumentTest.php b/tests/Document/DocumentTest.php
index e43b407e..e7025cc2 100644
--- a/tests/Document/DocumentTest.php
+++ b/tests/Document/DocumentTest.php
@@ -16,6 +16,7 @@
  * limitations under the License.
  */
 
+use \Mockery;
 use \stdClass;
 use \Neomerx\JsonApi\Schema\Link;
 use \Neomerx\Tests\JsonApi\BaseTestCase;
@@ -23,8 +24,8 @@
 use \Neomerx\JsonApi\Document\DocumentFactory;
 use \Neomerx\JsonApi\Contracts\Schema\LinkInterface;
 use \Neomerx\JsonApi\Contracts\Document\DocumentInterface;
-use \Neomerx\JsonApi\Document\Presenters\ElementPresenter;
 use \Neomerx\JsonApi\Contracts\Schema\SchemaFactoryInterface;
+use \Neomerx\JsonApi\Contracts\Schema\SchemaProviderInterface;
 use \Neomerx\JsonApi\Contracts\Document\DocumentFactoryInterface;
 
 /**
@@ -121,20 +122,14 @@ public function testSetMetaToDocument()
      */
     public function testAddToDataArrayedShowMembers()
     {
-        $this->document->addToData($resource = $this->schemaFactory->createResourceObject(
-            true,
+        $this->document->addToData($resource = $this->schemaFactory->createResourceObject($this->getSchema(
             'people',
             '123',
             ['firstName' => 'John', 'lastName' => 'Dow'],
-            ['some' => 'meta'],
-            'selfUrl',
-            true,
+            new Link('selfUrl'),
             true,
-            false,
-            false,
-            false,
-            false
-        ));
+            ['some' => 'meta']
+        ), new stdClass(), true));
         $this->document->setResourceCompleted($resource);
 
         $expected = <<<EOL
@@ -165,20 +160,15 @@ public function testAddToDataArrayedShowMembers()
      */
     public function testAddToDataNotArrayedHiddenMembers()
     {
-        $this->document->addToData($resource = $this->schemaFactory->createResourceObject(
-            false,
+        $this->document->addToData($resource = $this->schemaFactory->createResourceObject($this->getSchema(
             'people',
             '123',
             ['firstName' => 'John', 'lastName' => 'Dow'],
-            ['some' => 'meta'],
-            'selfUrl',
-            false,
-            false,
-            true,
-            true,
-            true,
-            true
-        ));
+            null, // self url
+            false, // show self
+            null   // meta
+        ), new stdClass(), false));
+
         $this->document->setResourceCompleted($resource);
 
         $expected = <<<EOL
@@ -231,46 +221,38 @@ public function testSetNullData()
      */
     public function testAddLinkToDataShowLinkMembers()
     {
-        $this->document->addToData($parent = $this->schemaFactory->createResourceObject(
-            false,
+        $this->document->addToData($parent = $this->schemaFactory->createResourceObject($this->getSchema(
             'people',
             '123',
             ['firstName' => 'John', 'lastName' => 'Dow'],
-            ['some' => 'author meta'],
-            'peopleSelfUrl/',
-            false,
-            false,
-            false,
-            false,
-            false,
-            false
-        ));
-        $resource = $this->schemaFactory->createResourceObject(
-            false,
+            new Link('peopleSelfUrl'), // self url
+            false, // show self
+            null // meta
+        ), new stdClass(), false));
+
+        $resource = $this->schemaFactory->createResourceObject($this->getSchema(
             'comments',
             '321',
-            ['title' => 'some title', 'body' => 'some body'],
-            ['some' => 'comment meta'],
-            'commentsSelfUrl/',
-            false,
+            null, // attributes
+            new Link('commentsSelfUrl/'),
+            true, // show self
+            ['this meta' => 'wont be included'],
             false,
             false,
-            false,
-            false,
-            false
-        );
+            null,
+            ['some' => 'comment meta']
+        ), new stdClass(), false);
+
         $link = $this->schemaFactory->createRelationshipObject(
             'comments-relationship',
             new stdClass(), // in reality it will be a Comment class instance where $resource properties were taken from
-            $this->createLink('selfSubUrl'),
-            $this->createLink('relatedSubUrl'),
-            false,
-            true,
-            true,
-            true,
-            true,
-            true,
-            [Link::FIRST => new Link('/first')]
+            [
+                LinkInterface::SELF    => $this->createLink('selfSubUrl'),
+                LinkInterface::RELATED => $this->createLink('relatedSubUrl'),
+                LinkInterface::FIRST   => new Link('/first', null, true),
+            ],
+            ['some' => 'relationship meta'],
+            true
         );
         $this->document->addRelationshipToData($parent, $link, $resource);
         $this->document->setResourceCompleted($parent);
@@ -286,13 +268,13 @@ public function testAddLinkToDataShowLinkMembers()
                 },
                 "relationships" : {
                     "comments-relationship" : {
+                        "data" : { "type" : "comments", "id" : "321", "meta" : { "some" : "comment meta" } },
+                        "meta" : { "some" : "relationship meta" },
                         "links"   : {
                             "self"    : "peopleSelfUrl/selfSubUrl",
                             "related" : "peopleSelfUrl/relatedSubUrl",
                             "first"   : "/first"
-                        },
-                        "data" : { "type" : "comments", "id" : "321" },
-                        "meta" : { "some" : "comment meta" }
+                        }
                     }
                 }
             }
@@ -302,50 +284,38 @@ public function testAddLinkToDataShowLinkMembers()
     }
 
     /**
-     * Test add link to 'data' section. Hide link members except linkage.
+     * Test add link to 'data' section. Hide link members except relationships.
      */
-    public function testAddLinkToDataHideLinkMembersExceptLinkage()
+    public function testAddLinkToDataHideLinkMembersExceptRelationships()
     {
-        $this->document->addToData($parent = $this->schemaFactory->createResourceObject(
-            false,
+        $this->document->addToData($parent = $this->schemaFactory->createResourceObject($this->getSchema(
             'people',
             '123',
             ['firstName' => 'John', 'lastName' => 'Dow'],
-            ['some' => 'author meta'],
-            'selfUrl/',
-            false,
-            false,
-            false,
-            false,
-            false,
-            false
-        ));
-        $resource = $this->schemaFactory->createResourceObject(
-            false,
+            new Link('selfUrl/'), // self url
+            false, // show self
+            null //   meta
+        ), new stdClass(), false));
+
+        $resource = $this->schemaFactory->createResourceObject($this->getSchema(
             'comments',
             '321',
-            ['title' => 'some title', 'body' => 'some body'],
-            ['some' => 'comment meta'],
-            'selfUrl/',
-            true,
-            true,
-            true,
-            true,
-            true,
-            true
-        );
+            null, // attributes
+            new Link('commentsSelfUrl/'),
+            true, // show self
+            ['this meta' => 'wont be shown'], // meta when resource is primary
+            false,
+            false,
+            ['this meta' => 'wont be shown'], // meta when resource within 'included'
+            ['some' => 'comment meta'] // meta when resource is in relationship
+        ), new stdClass(), false);
+
         $link = $this->schemaFactory->createRelationshipObject(
             'comments-relationship',
             new stdClass(), // in reality it will be a Comment class instance where $resource properties were taken from
-            $this->createLink('/selfSubUrl'),
-            $this->createLink('relatedSubUrl'),
-            false,
-            false,
-            false,
-            true,
-            false,
-            false,
-            null
+            [], //    links
+            null, //  relationship meta
+            true  //  show data
         );
         $this->document->addRelationshipToData($parent, $link, $resource);
         $this->document->setResourceCompleted($parent);
@@ -361,7 +331,7 @@ public function testAddLinkToDataHideLinkMembersExceptLinkage()
                 },
                 "relationships" : {
                     "comments-relationship" : {
-                        "data" : { "type" : "comments", "id" : "321", "meta" : {"some" : "comment meta"} }
+                        "data" : { "type" : "comments", "id" : "321", "meta" : { "some" : "comment meta" } }
                     }
                 }
             }
@@ -371,50 +341,38 @@ public function testAddLinkToDataHideLinkMembersExceptLinkage()
     }
 
     /**
-     * Test add multiple links to 'data' section. Hide link members except linkage.
+     * Test add multiple items to relationship's 'data' section. Hide link members except linkage.
      */
-    public function testAddMultipleLinksToData()
+    public function testAddMultipleRelationshipItemsToData()
     {
-        $this->document->addToData($parent = $this->schemaFactory->createResourceObject(
-            false,
+        $this->document->addToData($parent = $this->schemaFactory->createResourceObject($this->getSchema(
             'people',
             '123',
             ['firstName' => 'John', 'lastName' => 'Dow'],
-            ['some' => 'author meta'],
-            'selfUrl/',
-            false,
-            false,
-            false,
-            false,
-            false,
-            false
-        ));
-        $resource = $this->schemaFactory->createResourceObject(
-            false,
+            new Link('selfUrl/'), // self url
+            false, // show self
+            null //   meta
+        ), new stdClass(), false));
+
+        $resource = $this->schemaFactory->createResourceObject($this->getSchema(
             'comments',
             '321',
-            ['title' => 'some title', 'body' => 'some body'],
-            ['some' => 'comment meta'],
-            'selfUrl/',
-            true,
-            true,
-            true,
-            true,
-            true,
-            true
-        );
+            null, // attributes
+            new Link('selfUrlWillBeHidden/'),
+            true, // show self
+            ['this meta' => 'wont be shown'], // meta when resource is primary
+            false, // show 'self' in 'included'
+            false, // show relationships in 'included'
+            ['this meta' => 'wont be shown'], // meta when resource within 'included'
+            ['some' => 'comment meta'] // meta when resource is in relationship
+        ), new stdClass(), false);
+
         $link = $this->schemaFactory->createRelationshipObject(
             'comments-relationship',
             new stdClass(), // in reality it will be a Comment class instance where $resource properties were taken from
-            $this->createLink('/selfSubUrl'),
-            $this->createLink('relatedSubUrl'),
-            false,
-            false,
-            false,
-            true,
-            false,
-            false,
-            null
+            [], //    links
+            null, //  relationship meta
+            true   // show data
         );
         $this->document->addRelationshipToData($parent, $link, $resource);
         $this->document->addRelationshipToData($parent, $link, $resource);
@@ -448,47 +406,36 @@ public function testAddMultipleLinksToData()
      */
     public function testAddLinkToDataHideLinkMembersExceptMeta()
     {
-        $this->document->addToData($parent = $this->schemaFactory->createResourceObject(
-            false,
+        $this->document->addToData($parent = $this->schemaFactory->createResourceObject($this->getSchema(
             'people',
             '123',
             ['firstName' => 'John', 'lastName' => 'Dow'],
-            ['some' => 'author meta'],
-            'selfUrl',
-            false,
-            false,
-            false,
-            false,
-            false,
-            false
-        ));
-        $resource = $this->schemaFactory->createResourceObject(
-            false,
+            new Link('selfUrl/'), // self url
+            false, // show self
+            null //   meta
+        ), new stdClass(), false));
+
+        $resource = $this->schemaFactory->createResourceObject($this->getSchema(
             'comments',
             '321',
-            ['title' => 'some title', 'body' => 'some body'],
-            ['some' => 'comment meta'],
-            'selfUrl/',
-            true,
-            true,
-            true,
-            true,
-            true,
-            true
-        );
+            null, // attributes
+            new Link('selfUrlWillBeHidden/'),
+            true, // show self
+            ['this meta' => 'wont be shown'], // meta when resource is primary
+            false, // show 'self' in 'included'
+            false, // show relationships in 'included'
+            ['this meta' => 'wont be shown'], // meta when resource within 'included'
+            ['some' => 'comment meta'] // meta when resource is in relationship
+        ), new stdClass(), false);
+
         $link = $this->schemaFactory->createRelationshipObject(
             'comments-relationship',
             new stdClass(), // in reality it will be a Comment class instance where $resource properties were taken from
-            $this->createLink('selfSubUrl'),
-            $this->createLink('relatedSubUrl'),
-            false,
-            false,
-            false,
-            false,
-            true,
-            false,
-            null
+            [], //    links
+            ['some' => 'relationship meta'], //  relationship meta
+            false //  show data
         );
+
         $this->document->addRelationshipToData($parent, $link, $resource);
         $this->document->setResourceCompleted($parent);
 
@@ -503,7 +450,7 @@ public function testAddLinkToDataHideLinkMembersExceptMeta()
                 },
                 "relationships" : {
                     "comments-relationship" : {
-                        "meta" : { "some" : "comment meta" }
+                        "meta" : { "some" : "relationship meta" }
                     }
                 }
             }
@@ -512,105 +459,28 @@ public function testAddLinkToDataHideLinkMembersExceptMeta()
         $this->check($expected);
     }
 
-    /**
-     * Test add link as reference to 'data' section.
-     */
-    public function testAddReferenceToData()
-    {
-        $this->document->addToData($parent = $this->schemaFactory->createResourceObject(
-            false,
-            'people',
-            '123',
-            ['firstName' => 'John', 'lastName' => 'Dow'],
-            ['some' => 'author meta'],
-            'peopleSelfUrl',
-            false,
-            false,
-            false,
-            false,
-            false,
-            false
-        ));
-        $resource = $this->schemaFactory->createResourceObject(
-            false,
-            'comments',
-            '321',
-            ['title' => 'some title', 'body' => 'some body'],
-            ['some' => 'comment meta'],
-            'commentsSelfUrl',
-            false,
-            false,
-            false,
-            false,
-            false,
-            false
-        );
-        $link = $this->schemaFactory->createRelationshipObject(
-            'relationship-name',
-            new stdClass(), // in reality it will be a Comment class instance where $resource properties were taken from
-            $this->createLink('selfSubUrl'),
-            $this->createLink('relatedSubUrl'),
-            true,
-            true,
-            true,
-            true,
-            true,
-            false,
-            null
-        );
-        $this->document->addReferenceToData($parent, $link, $resource);
-        $this->document->setResourceCompleted($parent);
-
-        $expected = <<<EOL
-        {
-            "data" : {
-                "type"       : "people",
-                "id"         : "123",
-                "attributes" : {
-                    "firstName" : "John",
-                    "lastName"  : "Dow"
-                },
-                "relationships" : {
-                    "relationship-name" : "peopleSelfUrl/relatedSubUrl"
-                }
-            }
-        }
-EOL;
-        $this->check($expected);
-    }
-
     /**
      * Test add empty (empty array) link to 'data' section.
      */
     public function testAddEmptyLinkToData()
     {
-        $this->document->addToData($parent = $this->schemaFactory->createResourceObject(
-            false,
+        $this->document->addToData($parent = $this->schemaFactory->createResourceObject($this->getSchema(
             'people',
             '123',
             ['firstName' => 'John', 'lastName' => 'Dow'],
-            ['some' => 'author meta'],
-            'selfUrl',
-            false,
-            false,
-            false,
-            false,
-            false,
-            false
-        ));
+            new Link('peopleSelfUrl/'), // self url
+            false, // show self
+            null   // meta
+        ), new stdClass(), false));
+
         $link = $this->schemaFactory->createRelationshipObject(
             'relationship-name',
             new stdClass(), // in reality it will be a Comment class instance where $resource properties were taken from
-            $this->createLink('selfSubUrl'),
-            $this->createLink('relatedSubUrl'),
-            false,
-            false,
-            false,
-            false,
-            true,
-            false,
-            null
+            [], //    links
+            ['this meta' => 'wont be shown'], //  relationship meta
+            true   // show data
         );
+
         $this->document->addEmptyRelationshipToData($parent, $link);
         $this->document->setResourceCompleted($parent);
 
@@ -637,33 +507,23 @@ public function testAddEmptyLinkToData()
      */
     public function testAddNullLinkToData()
     {
-        $this->document->addToData($parent = $this->schemaFactory->createResourceObject(
-            false,
+        $this->document->addToData($parent = $this->schemaFactory->createResourceObject($this->getSchema(
             'people',
             '123',
             ['firstName' => 'John', 'lastName' => 'Dow'],
-            ['some' => 'author meta'],
-            'selfUrl',
-            false,
-            false,
-            false,
-            false,
-            false,
-            false
-        ));
+            new Link('peopleSelfUrl/'), // self url
+            false, // show self
+            null //   meta
+        ), new stdClass(), false));
+
         $link = $this->schemaFactory->createRelationshipObject(
             'relationship-name',
             new stdClass(), // in reality it will be a Comment class instance where $resource properties were taken from
-            $this->createLink('selfSubUrl'),
-            $this->createLink('relatedSubUrl'),
-            false,
-            false,
-            false,
-            false,
-            true,
-            false,
-            null
+            [], //    links
+            null, //  relationship meta
+            true   // show data
         );
+
         $this->document->addNullRelationshipToData($parent, $link);
         $this->document->setResourceCompleted($parent);
 
@@ -690,20 +550,18 @@ public function testAddNullLinkToData()
      */
     public function testAddToIncludedShowMembers()
     {
-        $this->document->addToIncluded($resource = $this->schemaFactory->createResourceObject(
-            false,
+        $this->document->addToIncluded($resource = $this->schemaFactory->createResourceObject($this->getSchema(
             'people',
             '123',
             ['firstName' => 'John', 'lastName' => 'Dow'],
-            ['some' => 'meta'],
-            'selfUrl',
-            false,
-            false,
-            true,
-            true,
-            true,
-            true
-        ));
+            new Link('peopleSelfUrl/'), // self url
+            false, // show self
+            null, //  meta
+            true, //  show 'self' in 'included'
+            false, // show 'relationships' in 'included'
+            ['some' => 'meta'] // meta when resource within 'included'
+        ), new stdClass(), false));
+
         $this->document->setResourceCompleted($resource);
 
         $expected = <<<EOL
@@ -718,7 +576,7 @@ public function testAddToIncludedShowMembers()
                         "lastName"  : "Dow"
                     },
                     "links" : {
-                        "self" : "selfUrl"
+                        "self" : "peopleSelfUrl/"
                     },
                     "meta" : {
                         "some" : "meta"
@@ -735,20 +593,15 @@ public function testAddToIncludedShowMembers()
      */
     public function testAddToIncludedHideMembers()
     {
-        $this->document->addToIncluded($resource = $this->schemaFactory->createResourceObject(
-            false,
+        $this->document->addToIncluded($resource = $this->schemaFactory->createResourceObject($this->getSchema(
             'people',
             '123',
             ['firstName' => 'John', 'lastName' => 'Dow'],
-            ['some' => 'meta'],
-            'selfUrl',
-            true,
-            true,
-            false,
-            false,
-            false,
-            false
-        ));
+            new Link('peopleSelfUrl/'), // self url
+            false, // show self
+            null //   meta
+        ), new stdClass(), false));
+
         $this->document->setResourceCompleted($resource);
 
         $expected = <<<EOL
@@ -774,47 +627,42 @@ public function testAddToIncludedHideMembers()
      */
     public function testAddLinkToIncludedShowLinkMembers()
     {
-        $this->document->addToIncluded($parent = $this->schemaFactory->createResourceObject(
-            false,
+        $this->document->addToIncluded($parent = $this->schemaFactory->createResourceObject($this->getSchema(
             'people',
             '123',
             ['firstName' => 'John', 'lastName' => 'Dow'],
-            ['some' => 'author meta'],
-            'peopleSelfUrl/',
-            false,
-            false,
-            true,
-            true,
-            true,
-            true
-        ));
-        $resource = $this->schemaFactory->createResourceObject(
-            false,
+            new Link('peopleSelfUrl'), // self url
+            false, // show self
+            ['this meta' => 'wont be shown'], // meta when primary resource
+            true, //  show 'self' in 'included'
+            false, // show 'relationships' in 'included'
+            ['some' => 'author meta'] // meta when resource within 'included'
+        ), new stdClass(), false));
+
+        $resource = $this->schemaFactory->createResourceObject($this->getSchema(
             'comments',
             '321',
-            ['title' => 'some title', 'body' => 'some body'],
-            ['some' => 'comment meta'],
-            'commentsSelfUrl/',
-            true,
-            true,
-            true,
-            true,
-            true,
-            true
-        );
+            null, // attributes
+            new Link('selfUrlWillBeHidden/'),
+            true, // show self
+            ['this meta' => 'wont be shown'], // meta when resource is primary
+            false, // show 'self' in 'included'
+            false, // show relationships in 'included'
+            ['this meta' => 'wont be shown'], // meta when resource within 'included'
+            ['some' => 'comment meta'] // meta when resource is in relationship
+        ), new stdClass(), false);
+
         $link = $this->schemaFactory->createRelationshipObject(
             'comments-relationship',
             new stdClass(), // in reality it will be a Comment class instance where $resource properties were taken from
-            $this->createLink('selfSubUrl'),
-            $this->createLink('relatedSubUrl'),
-            false,
-            true,
-            true,
-            true,
-            true,
-            false,
-            null
+            [
+                LinkInterface::SELF    => new Link('selfSubUrl'),
+                LinkInterface::RELATED => new Link('relatedSubUrl'),
+            ],
+            ['some' => 'relationship meta'], //  relationship meta
+            true  //  show data
         );
+
         $this->document->addRelationshipToIncluded($parent, $link, $resource);
         $this->document->setResourceCompleted($parent);
 
@@ -830,16 +678,16 @@ public function testAddLinkToIncludedShowLinkMembers()
                 },
                 "relationships" : {
                     "comments-relationship" : {
+                        "data" : { "type" : "comments", "id" : "321", "meta" : {"some" : "comment meta"} },
+                        "meta" : { "some" : "relationship meta" },
                         "links"   : {
                             "self"    : "peopleSelfUrl/selfSubUrl",
                             "related" : "peopleSelfUrl/relatedSubUrl"
-                        },
-                        "data" : { "type" : "comments", "id" : "321", "meta" : {"some" : "comment meta"} },
-                        "meta" : { "some" : "comment meta" }
+                        }
                     }
                 },
                 "links" : {
-                    "self" : "peopleSelfUrl/"
+                    "self" : "peopleSelfUrl"
                 },
                 "meta" : {
                     "some" : "author meta"
@@ -855,47 +703,39 @@ public function testAddLinkToIncludedShowLinkMembers()
      */
     public function testAddLinkToIncludedHideMembersForLinkedResource()
     {
-        $this->document->addToIncluded($parent = $this->schemaFactory->createResourceObject(
-            false,
+        $this->document->addToIncluded($parent = $this->schemaFactory->createResourceObject($this->getSchema(
             'people',
             '123',
             ['firstName' => 'John', 'lastName' => 'Dow'],
-            ['some' => 'author meta'],
-            'peopleSelfUrl/',
-            false,
-            false,
-            true,
-            true,
-            true,
-            true
-        ));
-        $resource = $this->schemaFactory->createResourceObject(
-            false,
+            new Link('peopleSelfUrl/'), // self url
+            false, // show self
+            ['this meta' => 'wont be shown'], // meta when primary resource
+            true, //  show 'self' in 'included'
+            false, // show 'relationships' in 'included'
+            ['some' => 'author meta'] // meta when resource within 'included'
+        ), new stdClass(), false));
+
+        $resource = $this->schemaFactory->createResourceObject($this->getSchema(
             'comments',
             '321',
-            ['title' => 'some title', 'body' => 'some body'],
-            ['some' => 'comment meta'],
-            'commentsSelfUrl/',
-            true,
-            true,
-            true,
-            true,
-            true,
-            true
-        );
+            null, // attributes
+            new Link('selfUrlWillBeHidden/'),
+            true, // show self
+            ['this meta' => 'wont be shown'], // meta when resource is primary
+            false, // show 'self' in 'included'
+            false, // show relationships in 'included'
+            ['this meta' => 'wont be shown'], // meta when resource within 'included'
+            ['some' => 'comment meta'] // meta when resource is in relationship
+        ), new stdClass(), false);
+
         $link = $this->schemaFactory->createRelationshipObject(
             'comments-relationship',
             new stdClass(), // in reality it will be a Comment class instance where $resource properties were taken from
-            $this->createLink('selfSubUrl'),
-            $this->createLink('relatedSubUrl'),
-            false,
-            false,
-            false,
-            true,
-            false,
-            false,
-            null
+            [], // links
+            null, //  relationship meta
+            true  //  show data
         );
+
         $this->document->addRelationshipToIncluded($parent, $link, $resource);
         $this->document->setResourceCompleted($parent);
 
@@ -931,33 +771,26 @@ public function testAddLinkToIncludedHideMembersForLinkedResource()
      */
     public function testAddEmptyLinkToIncluded()
     {
-        $this->document->addToIncluded($parent = $this->schemaFactory->createResourceObject(
-            false,
+        $this->document->addToIncluded($parent = $this->schemaFactory->createResourceObject($this->getSchema(
             'people',
             '123',
             ['firstName' => 'John', 'lastName' => 'Dow'],
-            ['some' => 'author meta'],
-            'peopleSelfUrl/',
-            false,
-            false,
-            true,
-            true,
-            true,
-            true
-        ));
+            new Link('peopleSelfUrl/'), // self url
+            false, // show self
+            ['this meta' => 'wont be shown'], // meta when primary resource
+            true, //  show 'self' in 'included'
+            false, // show 'relationships' in 'included'
+            ['some' => 'author meta'] // meta when resource within 'included'
+        ), new stdClass(), false));
+
         $link = $this->schemaFactory->createRelationshipObject(
-            'link-name',
+            'comments-relationship',
             new stdClass(), // in reality it will be a Comment class instance where $resource properties were taken from
-            $this->createLink('selfSubUrl'),
-            $this->createLink('relatedSubUrl'),
-            false,
-            true,
-            true,
-            true,
-            true,
-            false,
-            null
+            [], //    links
+            null, //  relationship meta
+            true   // show data
         );
+
         $this->document->addEmptyRelationshipToIncluded($parent, $link);
         $this->document->setResourceCompleted($parent);
 
@@ -972,7 +805,7 @@ public function testAddEmptyLinkToIncluded()
                     "lastName"  : "Dow"
                 },
                 "relationships" : {
-                    "link-name" : []
+                    "comments-relationship" : []
                 },
                 "links" : {
                     "self" : "peopleSelfUrl/"
@@ -991,94 +824,27 @@ public function testAddEmptyLinkToIncluded()
      */
     public function testAddNullLinkToIncluded()
     {
-        $this->document->addToIncluded($parent = $this->schemaFactory->createResourceObject(
-            false,
+        $this->document->addToIncluded($parent = $this->schemaFactory->createResourceObject($this->getSchema(
             'people',
             '123',
             ['firstName' => 'John', 'lastName' => 'Dow'],
-            ['some' => 'author meta'],
-            'peopleSelfUrl/',
-            false,
-            false,
-            true,
-            true,
-            true,
-            true
-        ));
-        $link = $this->schemaFactory->createRelationshipObject(
-            'link-name',
-            new stdClass(), // in reality it will be a Comment class instance where $resource properties were taken from
-            $this->createLink('selfSubUrl'),
-            $this->createLink('relatedSubUrl'),
-            false,
-            true,
-            true,
-            true,
-            true,
-            false,
-            null
-        );
-        $this->document->addNullRelationshipToIncluded($parent, $link);
-        $this->document->setResourceCompleted($parent);
+            new Link('peopleSelfUrl/'), // self url
+            false, // show self
+            ['this meta' => 'wont be shown'], // meta when primary resource
+            true, //  show 'self' in 'included'
+            false, // show 'relationships' in 'included'
+            ['some' => 'author meta'] // meta when resource within 'included'
+        ), new stdClass(), false));
 
-        $expected = <<<EOL
-        {
-            "data"     : null,
-            "included" : [{
-                "type"       : "people",
-                "id"         : "123",
-                "attributes" : {
-                    "firstName" : "John",
-                    "lastName"  : "Dow"
-                },
-                "relationships" : {
-                    "link-name" : null
-                },
-                "links" : {
-                    "self" : "peopleSelfUrl/"
-                },
-                "meta" : {
-                    "some" : "author meta"
-                }
-            }]
-        }
-EOL;
-        $this->check($expected);
-    }
-
-    /**
-     * Test add reference link to 'included' section.
-     */
-    public function testAddReferenceLinkToIncluded()
-    {
-        $this->document->addToIncluded($parent = $this->schemaFactory->createResourceObject(
-            false,
-            'people',
-            '123',
-            ['firstName' => 'John', 'lastName' => 'Dow'],
-            ['some' => 'author meta'],
-            'peopleSelfUrl/',
-            false,
-            false,
-            true,
-            true,
-            true,
-            true
-        ));
         $link = $this->schemaFactory->createRelationshipObject(
-            'relationship-name',
+            'comments-relationship',
             new stdClass(), // in reality it will be a Comment class instance where $resource properties were taken from
-            $this->createLink('selfSubUrl'),
-            $this->createLink('relatedSubUrl'),
-            true,
-            true,
-            true,
-            true,
-            true,
-            false,
-            null
+            [], // links
+            null, //  relationship meta
+            true  //  show data
         );
-        $this->document->addReferenceToIncluded($parent, $link);
+
+        $this->document->addNullRelationshipToIncluded($parent, $link);
         $this->document->setResourceCompleted($parent);
 
         $expected = <<<EOL
@@ -1092,7 +858,7 @@ public function testAddReferenceLinkToIncluded()
                     "lastName"  : "Dow"
                 },
                 "relationships" : {
-                    "relationship-name" : "peopleSelfUrl/relatedSubUrl"
+                    "comments-relationship" : null
                 },
                 "links" : {
                     "self" : "peopleSelfUrl/"
@@ -1111,20 +877,14 @@ public function testAddReferenceLinkToIncluded()
      */
     public function testAddTypeAndIdOnly()
     {
-        $this->document->addToData($resource = $this->schemaFactory->createResourceObject(
-            false,
+        $this->document->addToData($resource = $this->schemaFactory->createResourceObject($this->getSchema(
             'people',
             '123',
-            [],
-            null,
-            '',
-            false,
-            false,
-            false,
-            false,
-            false,
-            false
-        ));
+            null, // attributes
+            new Link('peopleSelfUrl/'), // self url
+            false, // show self
+            null // meta when primary resource
+        ), new stdClass(), false));
         $this->document->setResourceCompleted($resource);
 
         $expected = <<<EOL
@@ -1148,7 +908,7 @@ public function testAddError()
 
         $this->document->addError($this->documentFactory->createError(
             'some-id',
-            'some-href',
+            new Link('about-link'),
             'some-status',
             'some-code',
             'some-title',
@@ -1161,7 +921,7 @@ public function testAddError()
         {
             "errors":[{
                 "id"     : "some-id",
-                "href"   : "some-href",
+                "links"  : {"about" : "about-link"},
                 "status" : "some-status",
                 "code"   : "some-code",
                 "title"  : "some-title",
@@ -1175,16 +935,22 @@ public function testAddError()
     }
 
     /**
-     * Test URL concatenation.
+     * Test add JSON API version info.
      */
-    public function testConcatUrls()
+    public function testAddVersion()
     {
-        $presenter = new ElementPresenter();
+        $this->document->addJsonApiVersion('1.0', ['some' => 'meta']);
+        $this->document->unsetData();
 
-        $this->assertEquals('url/subUrl', $presenter->concatUrls('url', $this->createLink('subUrl')));
-        $this->assertEquals('url/subUrl', $presenter->concatUrls('url/', $this->createLink('subUrl')));
-        $this->assertEquals('url/subUrl', $presenter->concatUrls('url', $this->createLink('/subUrl')));
-        $this->assertEquals('url/subUrl', $presenter->concatUrls('url/', $this->createLink('/subUrl')));
+        $expected = <<<EOL
+        {
+            "jsonapi":{
+                "version" : "1.0",
+                "meta"    : { "some" : "meta" }
+            }
+        }
+EOL;
+        $this->check($expected);
     }
 
     /**
@@ -1196,20 +962,14 @@ public function testUnsetData()
             "some" => "values",
         ]);
 
-        $this->document->addToData($resource = $this->schemaFactory->createResourceObject(
-            true,
+        $this->document->addToData($resource = $this->schemaFactory->createResourceObject($this->getSchema(
             'people',
             '123',
-            ['firstName' => 'John', 'lastName' => 'Dow'],
-            ['some' => 'meta'],
-            'selfUrl',
-            true,
-            true,
-            false,
-            false,
-            false,
-            false
-        ));
+            null, // attributes
+            new Link('peopleSelfUrl/'), // self url
+            false, // show self
+            null // meta when primary resource
+        ), new stdClass(), false));
         $this->document->setResourceCompleted($resource);
 
         $this->document->unsetData();
@@ -1222,6 +982,110 @@ public function testUnsetData()
         $this->check($expected);
     }
 
+    /**
+     * Test add meta information to relationships.
+     */
+    public function testRelationshipsPrimaryMeta()
+    {
+        $this->document->addToData($parent = $this->schemaFactory->createResourceObject($this->getSchema(
+            'people',
+            '123',
+            ['firstName' => 'John', 'lastName' => 'Dow'],
+            new Link('peopleSelfUrl/'), // self url
+            false, // show self
+            null, //   meta
+            false, // show self in included
+            false, // show relationships in included
+            null, //  inclusion meta
+            null, //  relationship meta
+            [], //    include paths
+            true, //  show attributes in included
+            ['some' => 'relationships meta'] // relationships primary meta
+        ), new stdClass(), false));
+
+        $link = $this->schemaFactory->createRelationshipObject(
+            'relationship-name',
+            new stdClass(), // in reality it will be a Comment class instance where $resource properties were taken from
+            [], //    links
+            null, //  relationship meta
+            true   // show data
+        );
+
+        $this->document->addNullRelationshipToData($parent, $link);
+        $this->document->setResourceCompleted($parent);
+
+        $expected = <<<EOL
+        {
+            "data" : {
+                "type"       : "people",
+                "id"         : "123",
+                "attributes" : {
+                    "firstName" : "John",
+                    "lastName"  : "Dow"
+                },
+                "relationships" : {
+                    "relationship-name" : null,
+                    "meta" : { "some" : "relationships meta" }
+                }
+            }
+        }
+EOL;
+        $this->check($expected);
+    }
+
+    /**
+     * Test add meta information to relationships.
+     */
+    public function testRelationshipsInclusionMeta()
+    {
+        $this->document->addToIncluded($parent = $this->schemaFactory->createResourceObject($this->getSchema(
+            'people',
+            '123',
+            ['firstName' => 'John', 'lastName' => 'Dow'],
+            new Link('peopleSelfUrl/'), // self url
+            false, // show self
+            null, //   meta
+            false, // show self in included
+            false, // show relationships in included
+            null, //  inclusion meta
+            null, //  relationship meta
+            [], //    include paths
+            true, //  show attributes in included
+            null, //  relationships primary meta
+            ['some' => 'relationships meta'] // relationships inclusion meta
+        ), new stdClass(), false));
+
+        $link = $this->schemaFactory->createRelationshipObject(
+            'relationship-name',
+            new stdClass(), // in reality it will be a Comment class instance where $resource properties were taken from
+            [], //    links
+            null, //  relationship meta
+            true   // show data
+        );
+
+        $this->document->addNullRelationshipToIncluded($parent, $link);
+        $this->document->setResourceCompleted($parent);
+
+        $expected = <<<EOL
+        {
+            "data"     : null,
+            "included" : [{
+                "type"       : "people",
+                "id"         : "123",
+                "attributes" : {
+                    "firstName" : "John",
+                    "lastName"  : "Dow"
+                },
+                "relationships" : {
+                    "relationship-name" : null,
+                    "meta" : { "some" : "relationships meta" }
+                }
+            }]
+        }
+EOL;
+        $this->check($expected);
+    }
+
     /**
      * @param string $subHref
      *
@@ -1242,4 +1106,58 @@ private function check($expected)
         $actual   = json_encode($this->document->getDocument());
         $this->assertEquals($expected, $actual);
     }
+
+    /**
+     * @param string        $type
+     * @param string        $idx
+     * @param array|null    $attributes
+     * @param LinkInterface|null $selfLink
+     * @param bool          $showSelfUrl
+     * @param mixed         $primaryMeta
+     * @param bool          $showSelfInIncluded
+     * @param bool          $relShipsInIncluded
+     * @param mixed         $inclusionMeta
+     * @param mixed         $relationshipMeta
+     * @param array         $includePaths
+     * @param bool          $showAttributesInIncluded
+     * @param mixed         $relPrimaryMeta
+     * @param mixed         $relIncMeta
+     *
+     * @return SchemaProviderInterface
+     */
+    private function getSchema(
+        $type,
+        $idx,
+        $attributes,
+        $selfLink,
+        $showSelfUrl,
+        $primaryMeta,
+        $showSelfInIncluded = false,
+        $relShipsInIncluded = false,
+        $inclusionMeta = null,
+        $relationshipMeta = null,
+        $includePaths = [],
+        $showAttributesInIncluded = true,
+        $relPrimaryMeta = null,
+        $relIncMeta = null
+    ) {
+        $schema = Mockery::mock(SchemaProviderinterface::class);
+
+        $schema->shouldReceive('getResourceType')->zeroOrMoreTimes()->andReturn($type);
+        $schema->shouldReceive('getId')->zeroOrMoreTimes()->andReturn($idx);
+        $schema->shouldReceive('getSelfSubLink')->zeroOrMoreTimes()->andReturn($selfLink);
+        $schema->shouldReceive('getAttributes')->zeroOrMoreTimes()->andReturn($attributes);
+        $schema->shouldReceive('isShowSelf')->zeroOrMoreTimes()->andReturn($showSelfUrl);
+        $schema->shouldReceive('isShowSelfInIncluded')->zeroOrMoreTimes()->andReturn($showSelfInIncluded);
+        $schema->shouldReceive('isShowAttributesInIncluded')->zeroOrMoreTimes()->andReturn($showAttributesInIncluded);
+        $schema->shouldReceive('isShowRelationshipsInIncluded')->zeroOrMoreTimes()->andReturn($relShipsInIncluded);
+        $schema->shouldReceive('getIncludePaths')->zeroOrMoreTimes()->andReturn($includePaths);
+        $schema->shouldReceive('getPrimaryMeta')->zeroOrMoreTimes()->andReturn($primaryMeta);
+        $schema->shouldReceive('getLinkageMeta')->zeroOrMoreTimes()->andReturn($relationshipMeta);
+        $schema->shouldReceive('getInclusionMeta')->zeroOrMoreTimes()->andReturn($inclusionMeta);
+        $schema->shouldReceive('getRelationshipsPrimaryMeta')->zeroOrMoreTimes()->andReturn($relPrimaryMeta);
+        $schema->shouldReceive('getRelationshipsInclusionMeta')->zeroOrMoreTimes()->andReturn($relIncMeta);
+
+        return $schema;
+    }
 }
diff --git a/tests/Document/FactoryTest.php b/tests/Document/FactoryTest.php
index ca8bdcd0..8b838136 100644
--- a/tests/Document/FactoryTest.php
+++ b/tests/Document/FactoryTest.php
@@ -16,8 +16,10 @@
  * limitations under the License.
  */
 
+use \Neomerx\JsonApi\Schema\Link;
 use \Neomerx\Tests\JsonApi\BaseTestCase;
 use \Neomerx\JsonApi\Document\DocumentFactory;
+use \Neomerx\JsonApi\Contracts\Document\DocumentInterface;
 use \Neomerx\JsonApi\Contracts\Document\DocumentFactoryInterface;
 
 /**
@@ -55,7 +57,7 @@ public function testCreateError()
     {
         $this->assertNotNull($error = $this->factory->createError(
             $idx = 'some-id',
-            $href = 'some-href',
+            $link = new Link('about-link'),
             $status = 'some-status',
             $code = 'some-code',
             $title = 'some-title',
@@ -65,7 +67,7 @@ public function testCreateError()
         ));
 
         $this->assertEquals($idx, $error->getId());
-        $this->assertEquals($href, $error->getHref());
+        $this->assertEquals([DocumentInterface::KEYWORD_ERRORS_ABOUT => $link], $error->getLinks());
         $this->assertEquals($status, $error->getStatus());
         $this->assertEquals($code, $error->getCode());
         $this->assertEquals($title, $error->getTitle());
diff --git a/tests/Encoder/EncodeErrorsTest.php b/tests/Encoder/EncodeErrorsTest.php
index cb22685b..f4368a43 100644
--- a/tests/Encoder/EncodeErrorsTest.php
+++ b/tests/Encoder/EncodeErrorsTest.php
@@ -16,6 +16,7 @@
  * limitations under the License.
  */
 
+use \Neomerx\JsonApi\Schema\Link;
 use \Neomerx\JsonApi\Document\Error;
 use \Neomerx\JsonApi\Encoder\Encoder;
 use \Neomerx\Tests\JsonApi\Data\Author;
@@ -34,7 +35,7 @@ public function testEncodeError()
     {
         $error = new Error(
             'some-id',
-            'some-href',
+            new Link('about-link'),
             'some-status',
             'some-code',
             'some-title',
@@ -51,7 +52,7 @@ public function testEncodeError()
         {
             "errors":[{
                 "id"     : "some-id",
-                "href"   : "some-href",
+                "links"  : {"about" : "about-link"},
                 "status" : "some-status",
                 "code"   : "some-code",
                 "title"  : "some-title",
@@ -74,7 +75,7 @@ public function testEncodeErrors()
     {
         $error = new Error(
             'some-id',
-            'some-href',
+            new Link('about-link'),
             'some-status',
             'some-code',
             'some-title',
@@ -93,7 +94,7 @@ public function testEncodeErrors()
         {
             "errors":[{
                 "id"     : "some-id",
-                "href"   : "some-href",
+                "links"  : {"about" : "about-link"},
                 "status" : "some-status",
                 "code"   : "some-code",
                 "title"  : "some-title",
diff --git a/tests/Encoder/EncodeIncludedObjectsTest.php b/tests/Encoder/EncodeIncludedObjectsTest.php
index 9945d0a6..3530afdb 100644
--- a/tests/Encoder/EncodeIncludedObjectsTest.php
+++ b/tests/Encoder/EncodeIncludedObjectsTest.php
@@ -25,6 +25,7 @@
 use \Neomerx\Tests\JsonApi\Data\Comment;
 use \Neomerx\Tests\JsonApi\Data\PostSchema;
 use \Neomerx\Tests\JsonApi\Data\SiteSchema;
+use \Neomerx\JsonApi\Encoder\EncoderOptions;
 use \Neomerx\Tests\JsonApi\Data\AuthorSchema;
 use \Neomerx\Tests\JsonApi\Data\CommentSchema;
 use \Neomerx\JsonApi\Parameters\EncodingParameters;
@@ -54,6 +55,11 @@ class EncodeIncludedObjectsTest extends BaseTestCase
      */
     private $site;
 
+    /**
+     * @var EncoderOptions
+     */
+    private $encoderOptions;
+
     /**
      * Set up.
      */
@@ -74,6 +80,7 @@ protected function setUp()
             $this->comments
         );
         $this->site = Site::instance(2, 'site name', [$this->post]);
+        $this->encoderOptions = new EncoderOptions(0, 'http://example.com');
     }
 
     /**
@@ -93,7 +100,7 @@ public function testEncodeWithIncludedObjects()
                 $schema->setIncludePaths([Post::LINK_COMMENTS]);
                 return $schema;
             },
-        ])->encode($this->post);
+        ], $this->encoderOptions)->encode($this->post);
 
         $expected = <<<EOL
         {
@@ -158,7 +165,7 @@ public function testEncodeWithRecursiveIncludedObjects()
             Comment::class => CommentSchema::class,
             Post::class    => PostSchema::class,
             Site::class    => SiteSchema::class,
-        ])->encode($this->site, null, null, new EncodingParameters(
+        ], $this->encoderOptions)->encode($this->site, null, null, new EncodingParameters(
             // include only this relation (according to the spec intermediate will be included as well)
             [Site::LINK_POSTS . '.' . Post::LINK_COMMENTS],
             // include only these attributes and links
@@ -238,7 +245,7 @@ public function testEncodeWithNullAndEmptyLinks()
             Comment::class => CommentSchema::class,
             Post::class    => PostSchema::class,
             Site::class    => SiteSchema::class,
-        ])->encode($this->site);
+        ], $this->encoderOptions)->encode($this->site);
 
         $expected = <<<EOL
         {
@@ -295,7 +302,7 @@ public function testEncodeDuplicatesWithCyclicDeps()
             Comment::class => CommentSchema::class,
             Post::class    => PostSchema::class,
             Site::class    => SiteSchema::class,
-        ])->encode($this->site);
+        ], $this->encoderOptions)->encode($this->site);
 
         $expected = <<<EOL
         {
@@ -339,63 +346,6 @@ public function testEncodeDuplicatesWithCyclicDeps()
         $this->assertEquals($expected, $actual);
     }
 
-    /**
-     * Test encode included objects with reference links.
-     */
-    public function testEncodeLinksAsRefs()
-    {
-        $actual = Encoder::instance([
-            Author::class  => AuthorSchema::class,
-            Comment::class => CommentSchema::class,
-            Post::class    => function ($factory, $container) {
-                $schema = new PostSchema($factory, $container);
-                $schema->linkAddTo(Post::LINK_AUTHOR, PostSchema::SHOW_AS_REF, true);
-                $schema->linkAddTo(Post::LINK_COMMENTS, PostSchema::SHOW_AS_REF, true);
-                return $schema;
-            },
-            Site::class => SiteSchema::class,
-        ])->encode($this->site);
-
-        $expected = <<<EOL
-        {
-            "data" : {
-                "type"  : "sites",
-                "id"    : "2",
-                "attributes" : {
-                    "name"  : "site name"
-                },
-                "relationships" : {
-                    "posts" : {
-                        "data" : {
-                            "type" : "posts",
-                            "id" : "1"
-                        }
-                    }
-                },
-                "links" : {
-                    "self" : "http://example.com/sites/2"
-                }
-            },
-            "included" : [{
-                "type"  : "posts",
-                "id"    : "1",
-                "attributes" : {
-                    "title" : "JSON API paints my bikeshed!",
-                    "body"  : "Outside every fat man there was an even fatter man trying to close in"
-                },
-                "relationships" : {
-                    "author"   : "http://example.com/posts/1/author",
-                    "comments" : "http://example.com/posts/1/comments"
-                }
-            }]
-        }
-EOL;
-        // remove formatting from 'expected'
-        $expected = json_encode(json_decode($expected));
-
-        $this->assertEquals($expected, $actual);
-    }
-
     /**
      * Test link objects that should not be included but these objects link to others that should.
      * Parser should stop parsing even if deeper objects exist.
@@ -411,7 +361,7 @@ public function testEncodeLinkNonIncludableWithIncludableLinks()
                 $schema->setIncludePaths([Site::LINK_POSTS]);
                 return $schema;
             },
-        ])->encode($this->site);
+        ], $this->encoderOptions)->encode($this->site);
 
         $expected = <<<EOL
         {
@@ -472,13 +422,12 @@ public function testEncodeWithLinkWithPagination()
                 $schema = new PostSchema($factory, $container);
                 $schema->linkAddTo(
                     Post::LINK_COMMENTS,
-                    PostSchema::PAGINATION,
-                    [Link::FIRST => new Link('/first')]
+                    PostSchema::LINKS,
+                    [Link::FIRST => new Link('comments/first')]
                 );
-                $schema->linkAddTo(Post::LINK_COMMENTS, PostSchema::SHOW_PAGINATION, true);
                 return $schema;
             },
-        ])->encode($this->post);
+        ], $this->encoderOptions)->encode($this->post);
 
         $expected = <<<EOL
         {
@@ -499,7 +448,7 @@ public function testEncodeWithLinkWithPagination()
                             { "type" : "comments", "id" : "12" }
                         ],
                         "links" : {
-                            "first" : "/first"
+                            "first" : "http://example.com/posts/1/comments/first"
                         }
                     }
                 },
diff --git a/tests/Encoder/EncodeSimpleObjectsTest.php b/tests/Encoder/EncodeSimpleObjectsTest.php
index 26b0a045..fcff1830 100644
--- a/tests/Encoder/EncodeSimpleObjectsTest.php
+++ b/tests/Encoder/EncodeSimpleObjectsTest.php
@@ -20,14 +20,26 @@
 use \Neomerx\JsonApi\Encoder\Encoder;
 use \Neomerx\Tests\JsonApi\Data\Author;
 use \Neomerx\Tests\JsonApi\BaseTestCase;
+use \Neomerx\JsonApi\Encoder\EncoderOptions;
 use \Neomerx\Tests\JsonApi\Data\AuthorSchema;
-use \Neomerx\JsonApi\Encoder\JsonEncodeOptions;
 
 /**
  * @package Neomerx\Tests\JsonApi
  */
 class EncodeSimpleObjectsTest extends BaseTestCase
 {
+    /**
+     * @var EncoderOptions
+     */
+    private $encoderOptions;
+
+    protected function setUp()
+    {
+        parent::setUp();
+
+        $this->encoderOptions = new EncoderOptions(0, 'http://example.com');
+    }
+
     /**
      * Test encode null.
      */
@@ -84,7 +96,7 @@ public function testEncodeObjectWithAttributesOnly()
                 $schema->linkRemove(Author::LINK_COMMENTS);
                 return $schema;
             }
-        ]);
+        ], $this->encoderOptions);
 
         $actual = $endcoder->encode($author);
 
@@ -121,7 +133,7 @@ public function testEncodeObjectWithAttributesOnlyInArray()
                 $schema->linkRemove(Author::LINK_COMMENTS);
                 return $schema;
             }
-        ]);
+        ], $this->encoderOptions);
 
         $actual = $endcoder->encode([$author]);
 
@@ -158,7 +170,7 @@ public function testEncodeObjectWithAttributesOnlyPrettyPrinted()
                 $schema->linkRemove(Author::LINK_COMMENTS);
                 return $schema;
             }
-        ], new JsonEncodeOptions(JSON_PRETTY_PRINT));
+        ], new EncoderOptions(JSON_PRETTY_PRINT, 'http://example.com'));
 
         $actual = $endcoder->encode($author);
 
@@ -194,7 +206,7 @@ public function testEncodeArrayOfObjectsWithAttributesOnly()
                 $schema->linkRemove(Author::LINK_COMMENTS);
                 return $schema;
             }
-        ]);
+        ], $this->encoderOptions);
 
         $actual = $endcoder->encode([$author1, $author2]);
 
@@ -238,7 +250,7 @@ public function testEncodeArrayOfObjectsWithAttributesOnly()
     public function testEncodeMetaAndtopLinksForSimpleObject()
     {
         $author = Author::instance(9, 'Dan', 'Gebhardt');
-        $links  = [Link::SELF => new Link('http://example.com/people/9')];
+        $links  = [Link::SELF => new Link('/people/9')];
         $meta   = [
             "copyright" => "Copyright 2015 Example Corp.",
             "authors"   => [
@@ -254,7 +266,7 @@ public function testEncodeMetaAndtopLinksForSimpleObject()
                 $schema->linkRemove(Author::LINK_COMMENTS);
                 return $schema;
             }
-        ])->encode($author, $links, $meta);
+        ], $this->encoderOptions)->encode($author, $links, $meta);
 
         $expected = <<<EOL
         {
@@ -327,4 +339,25 @@ public function testEncodeMeta()
 
         $this->assertEquals($expected, $actual);
     }
+
+    public function testEncodeJsonApiVersion()
+    {
+        $endcoder = Encoder::instance([], new EncoderOptions(0, null, true, ['some' => 'meta']));
+
+        $actual = $endcoder->encode(null);
+
+        $expected = <<<EOL
+        {
+            "jsonapi" : {
+                "version" : "1.0",
+                "meta"    : { "some" : "meta" }
+            },
+            "data" : null
+        }
+EOL;
+        // remove formatting from 'expected'
+        $expected = json_encode(json_decode($expected));
+
+        $this->assertEquals($expected, $actual);
+    }
 }
diff --git a/tests/Encoder/EncodeSparseAndFieldSetsTest.php b/tests/Encoder/EncodeSparseAndFieldSetsTest.php
index 1af0e655..8dadaf64 100644
--- a/tests/Encoder/EncodeSparseAndFieldSetsTest.php
+++ b/tests/Encoder/EncodeSparseAndFieldSetsTest.php
@@ -24,6 +24,7 @@
 use \Neomerx\Tests\JsonApi\Data\Comment;
 use \Neomerx\Tests\JsonApi\Data\PostSchema;
 use \Neomerx\Tests\JsonApi\Data\SiteSchema;
+use \Neomerx\JsonApi\Encoder\EncoderOptions;
 use \Neomerx\Tests\JsonApi\Data\AuthorSchema;
 use \Neomerx\Tests\JsonApi\Data\CommentSchema;
 use \Neomerx\JsonApi\Parameters\EncodingParameters;
@@ -53,6 +54,11 @@ class EncodeSparseAndFieldSetsTest extends BaseTestCase
      */
     private $site;
 
+    /**
+     * @var EncoderOptions
+     */
+    private $encoderOptions;
+
     /**
      * Set up.
      */
@@ -73,6 +79,7 @@ protected function setUp()
             $this->comments
         );
         $this->site = Site::instance(2, 'site name', [$this->post]);
+        $this->encoderOptions = new EncoderOptions(0, 'http://example.com');
     }
 
     /**
@@ -87,7 +94,7 @@ public function testEncodeWithRecursiveIncludedObjects()
             Comment::class => CommentSchema::class,
             Post::class    => PostSchema::class,
             Site::class    => SiteSchema::class,
-        ])->encode($this->site, null, null, new EncodingParameters(
+        ], $this->encoderOptions)->encode($this->site, null, null, new EncodingParameters(
             [
                 // include only this relations
                 Site::LINK_POSTS,
@@ -183,7 +190,7 @@ public function testEncodeOnlyFieldSets()
             Comment::class => CommentSchema::class,
             Post::class    => PostSchema::class,
             Site::class    => SiteSchema::class,
-        ])->encode($this->site, null, null, new EncodingParameters(
+        ], $this->encoderOptions)->encode($this->site, null, null, new EncodingParameters(
             null,
             // include only these attributes and links
             [
diff --git a/tests/Encoder/EncoderTest.php b/tests/Encoder/EncoderTest.php
index 5c333098..98ca69a6 100644
--- a/tests/Encoder/EncoderTest.php
+++ b/tests/Encoder/EncoderTest.php
@@ -16,21 +16,39 @@
  * limitations under the License.
  */
 
+use \Neomerx\JsonApi\Schema\Link;
 use \Neomerx\JsonApi\Encoder\Encoder;
 use \Neomerx\Tests\JsonApi\Data\Post;
 use \Neomerx\Tests\JsonApi\Data\Author;
 use \Neomerx\Tests\JsonApi\Data\Comment;
 use \Neomerx\Tests\JsonApi\BaseTestCase;
 use \Neomerx\Tests\JsonApi\Data\PostSchema;
+use \Neomerx\JsonApi\Encoder\EncoderOptions;
 use \Neomerx\Tests\JsonApi\Data\AuthorSchema;
 use \Neomerx\Tests\JsonApi\Data\CommentSchema;
 use \Neomerx\JsonApi\Parameters\EncodingParameters;
+use \Neomerx\JsonApi\Contracts\Schema\LinkInterface;
 
 /**
  * @package Neomerx\Tests\JsonApi
  */
 class EncoderTest extends BaseTestCase
 {
+    /**
+     * @var EncoderOptions
+     */
+    private $encoderOptions;
+
+    /**
+     * Set up.
+     */
+    protected function setUp()
+    {
+        parent::setUp();
+
+        $this->encoderOptions = new EncoderOptions(0, 'http://example.com');
+    }
+
     /**
      * Test encode array of simple objects with attributes only.
      */
@@ -43,7 +61,7 @@ public function testEncodeArrayOfDuplicateObjectsWithAttributesOnly()
                 $schema->linkRemove(Author::LINK_COMMENTS);
                 return $schema;
             }
-        ]);
+        ], $this->encoderOptions);
 
         $actual = $endcoder->encode([$author, $author]);
 
@@ -98,7 +116,7 @@ public function testEncodeDuplicatesWithCircularReferencesInData()
                 $schema->setIncludePaths([]);
                 return $schema;
             },
-        ]);
+        ], $this->encoderOptions);
 
         $actual = $endcoder->encode([$author, $author]);
 
@@ -158,7 +176,7 @@ public function testEncodeDuplicatesWithRelationFieldSetFilter()
         $endcoder = Encoder::instance([
             Author::class  => AuthorSchema::class,
             Comment::class => CommentSchema::class,
-        ]);
+        ], $this->encoderOptions);
 
         $author->{Author::LINK_COMMENTS} = $comments;
 
@@ -210,7 +228,7 @@ public function testEncodeSimpleLinks()
             Author::class  => AuthorSchema::class,
             Comment::class => CommentSchema::class,
             Post::class    => PostSchema::class,
-        ])->encode($this->getStandardPost());
+        ], $this->encoderOptions)->encode($this->getStandardPost());
 
         $expected = <<<EOL
         {
@@ -244,47 +262,6 @@ public function testEncodeSimpleLinks()
         $this->assertEquals($expected, $actual);
     }
 
-    /**
-     * Test encode resource object links as references.
-     */
-    public function testEncodeLinkAsReference()
-    {
-        $actual = Encoder::instance([
-            Author::class  => AuthorSchema::class,
-            Comment::class => CommentSchema::class,
-            Post::class    => function ($factory, $container) {
-                $schema = new PostSchema($factory, $container);
-                $schema->linkAddTo(Post::LINK_AUTHOR, PostSchema::SHOW_AS_REF, true);
-                $schema->linkAddTo(Post::LINK_COMMENTS, PostSchema::SHOW_AS_REF, true);
-                return $schema;
-            },
-        ])->encode($this->getStandardPost());
-
-        $expected = <<<EOL
-        {
-            "data" : {
-                "type" : "posts",
-                "id"   : "1",
-                "attributes" : {
-                    "title" : "JSON API paints my bikeshed!",
-                    "body"  : "Outside every fat man there was an even fatter man trying to close in"
-                },
-                "relationships" : {
-                    "author"   : "http://example.com/posts/1/author",
-                    "comments" : "http://example.com/posts/1/comments"
-                },
-                "links" : {
-                    "self"     : "http://example.com/posts/1"
-                }
-            }
-        }
-EOL;
-        // remove formatting from 'expected'
-        $expected = json_encode(json_decode($expected));
-
-        $this->assertEquals($expected, $actual);
-    }
-
     /**
      * Test encode resource object links as references.
      */
@@ -299,7 +276,7 @@ public function testEncodeEmptyLinks()
                 $schema->linkAddTo(Post::LINK_COMMENTS, PostSchema::DATA, []);
                 return $schema;
             },
-        ])->encode($this->getStandardPost());
+        ], $this->encoderOptions)->encode($this->getStandardPost());
 
         $expected = <<<EOL
         {
@@ -342,7 +319,7 @@ public function testEncodeLinksInDocumentAndRelationships()
                 $schema->linkAddTo(Post::LINK_COMMENTS, PostSchema::SHOW_RELATED, true);
                 return $schema;
             },
-        ])->encode($this->getStandardPost());
+        ], $this->encoderOptions)->encode($this->getStandardPost());
 
         $expected = <<<EOL
         {
@@ -355,21 +332,21 @@ public function testEncodeLinksInDocumentAndRelationships()
                 },
                 "relationships" : {
                     "author" : {
+                        "data" : { "type" : "people", "id" : "9" },
                         "links" : {
                             "self"    : "http://example.com/posts/1/relationships/author",
                             "related" : "http://example.com/posts/1/author"
-                        },
-                        "data" : { "type" : "people", "id" : "9" }
+                        }
                     },
                     "comments" : {
-                        "links" : {
-                            "self"    : "http://example.com/posts/1/relationships/comments",
-                            "related" : "http://example.com/posts/1/comments"
-                        },
                         "data":[
                             { "type" : "comments", "id" : "5" },
                             { "type" : "comments", "id" : "12" }
-                        ]
+                        ],
+                        "links" : {
+                            "self"    : "http://example.com/posts/1/relationships/comments",
+                            "related" : "http://example.com/posts/1/comments"
+                        }
                     }
                 },
                 "links" : {
@@ -397,12 +374,16 @@ public function testEncodeLinkWithMeta()
         $actual = Encoder::instance([
             Author::class  => function ($factory, $container) {
                 $schema = new AuthorSchema($factory, $container);
-                $schema->linkAddTo(Post::LINK_COMMENTS, PostSchema::SHOW_SELF, true);
-                $schema->setRelationshipMeta(['some' => 'meta']);
+                $schema->linkAddTo(Author::LINK_COMMENTS, AuthorSchema::SHOW_SELF, true);
+                $schema->linkAddTo(
+                    Author::LINK_COMMENTS,
+                    AuthorSchema::LINKS,
+                    [LinkInterface::SELF => new Link('relationships/comments', ['some' => 'meta'])]
+                );
                 return $schema;
             },
             Comment::class => CommentSchema::class,
-        ])->encode($author);
+        ], $this->encoderOptions)->encode($author);
 
         $expected = <<<EOL
         {
@@ -415,20 +396,70 @@ public function testEncodeLinkWithMeta()
                 },
                 "relationships" : {
                     "comments"  : {
+                        "data":[
+                            { "type" : "comments", "id" : "5" },
+                            { "type" : "comments", "id" : "12" }
+                        ],
                         "links" : {
                             "self" : {
                                 "href" : "http://example.com/people/9/relationships/comments",
-                                "meta" : { "some":"meta" }
+                                "meta" : { "some" : "meta" }
                             }
-                        },
-                        "data":[
+                        }
+                    }
+                },
+                "links":{
+                    "self":"http://example.com/people/9"
+                }
+            }
+        }
+EOL;
+        // remove formatting from 'expected'
+        $expected = json_encode(json_decode($expected));
+
+        $this->assertEquals($expected, $actual);
+    }
+
+    /**
+     * Test relationships meta.
+     */
+    public function testRelationshipsMeta()
+    {
+        $actual = Encoder::instance([
+            Author::class  => AuthorSchema::class,
+            Comment::class => CommentSchema::class,
+            Post::class    => function ($factory, $container) {
+                $schema = new PostSchema($factory, $container);
+                $schema->setRelationshipsMeta(['some' => 'meta']);
+                return $schema;
+            },
+        ], $this->encoderOptions)->encode($this->getStandardPost());
+
+        $expected = <<<EOL
+        {
+            "data" : {
+                "type"  : "posts",
+                "id"    : "1",
+                "attributes" : {
+                    "title" : "JSON API paints my bikeshed!",
+                    "body"  : "Outside every fat man there was an even fatter man trying to close in"
+                },
+                "relationships" : {
+                    "author" : {
+                        "data" : { "type" : "people", "id" : "9" }
+                    },
+                    "comments" : {
+                        "data" : [
                             { "type":"comments", "id":"5" },
                             { "type":"comments", "id":"12" }
                         ]
+                    },
+                    "meta" : {
+                        "some" : "meta"
                     }
                 },
-                "links":{
-                    "self":"http://example.com/people/9"
+                "links" : {
+                    "self" : "http://example.com/posts/1"
                 }
             }
         }
@@ -439,6 +470,15 @@ public function testEncodeLinkWithMeta()
         $this->assertEquals($expected, $actual);
     }
 
+    /**
+     * Test get encoder options.
+     */
+    public function testGetEncoderOptions()
+    {
+        $endoder = Encoder::instance([], $this->encoderOptions);
+        $this->assertSame($this->encoderOptions, $endoder->getEncoderOptions());
+    }
+
     /**
      * @return Post
      */
diff --git a/tests/Encoder/Parser/ParserTest.php b/tests/Encoder/Parser/ParserTest.php
index 1252a357..5c49ce25 100644
--- a/tests/Encoder/Parser/ParserTest.php
+++ b/tests/Encoder/Parser/ParserTest.php
@@ -83,12 +83,7 @@ protected function setUp()
         $schemas = [
             Author::class  => AuthorSchema::class,
             Comment::class => CommentSchema::class,
-            Post::class    => function ($factory, $container) {
-                $schema = new PostSchema($factory, $container);
-                $schema->linkAddTo(Post::LINK_AUTHOR, PostSchema::SHOW_AS_REF, true);
-                $schema->linkAddTo(Post::LINK_COMMENTS, PostSchema::SHOW_AS_REF, true);
-                return $schema;
-            },
+            Post::class    => PostSchema::class,
         ];
         $container    = (new SchemaFactory())->createContainer($schemas);
         $this->parser = (new EncoderFactory())->createParser($container);
@@ -204,33 +199,6 @@ public function testParseObjectWithCircularReferences()
         $this->assertEquals($expected, $allReplies);
     }
 
-    /**
-     * Test parse link as reference.
-     */
-    public function testParseLinkReferences()
-    {
-        $this->post->{Post::LINK_AUTHOR} = null;
-
-        $start      = ParserReplyInterface::REPLY_TYPE_RESOURCE_STARTED;
-        $startRef   = ParserReplyInterface::REPLY_TYPE_REFERENCE_STARTED;
-        $startNull  = ParserReplyInterface::REPLY_TYPE_NULL_RESOURCE_STARTED;
-        $complete   = ParserReplyInterface::REPLY_TYPE_RESOURCE_COMPLETED;
-        $expected   = [
-            //             level link name   type        id    attributes         meta
-            [$start,       1,    '',         'posts',    1,    $this->postAttr, null],
-            [$startNull,   2,    'author',   '',         null, null,            null],
-            [$startRef,    2,    'comments', '',         null, null,            null],
-            [$complete,    1,    '',         'posts',    1,    $this->postAttr, null],
-        ];
-
-        $allReplies = [];
-        foreach ($this->parser->parse($this->post) as $reply) {
-            /** @var ParserReplyInterface $reply */
-            $allReplies[] = $this->replyToArray($reply);
-        }
-        $this->assertEquals($expected, $allReplies);
-    }
-
     /**
      * @param ParserReplyInterface $reply
      *
@@ -248,7 +216,7 @@ private function replyToArray(ParserReplyInterface $reply)
             $resource === null ? null : $resource->getType(),
             $resource === null ? null : $resource->getId(),
             $resource === null ? null : $resource->getAttributes(),
-            $resource === null ? null : $resource->getMeta(),
+            $resource === null ? null : $resource->getPrimaryMeta(),
         ];
     }
 }
diff --git a/tests/Parameters/ParameterParserTest.php b/tests/Parameters/ParameterParserTest.php
index 4b7a0267..57c36d4a 100644
--- a/tests/Parameters/ParameterParserTest.php
+++ b/tests/Parameters/ParameterParserTest.php
@@ -84,6 +84,8 @@ public function testHeadersWithNoExtensionsAndParameters()
         $this->assertNull($parameters->getSortParameters());
         $this->assertNull($parameters->getFilteringParameters());
         $this->assertNull($parameters->getPaginationParameters());
+
+        $this->assertTrue($parameters->isEmpty());
     }
 
     /**
diff --git a/tests/Parameters/RestrictiveParameterCheckerTest.php b/tests/Parameters/RestrictiveParameterCheckerTest.php
index 5e394ba3..71353aa5 100644
--- a/tests/Parameters/RestrictiveParameterCheckerTest.php
+++ b/tests/Parameters/RestrictiveParameterCheckerTest.php
@@ -52,11 +52,11 @@ class RestrictiveParameterCheckerTest extends BaseTestCase
      * @var array
      */
     private $requestParams = [
-        'fields'  => ['type1' => 'fields1,fields2'],
-        'include' => 'author,comments,comments.author',
-        'sort'    => '-created,+title,name.with.dots',
-        'filter'  => ['some' => 'filter'],
-        'page'    => ['size' => 10, 'offset' => 4],
+        ParametersParserInterface::PARAM_FIELDS  => ['type1' => 'fields1,fields2'],
+        ParametersParserInterface::PARAM_INCLUDE => 'author,comments,comments.author',
+        ParametersParserInterface::PARAM_SORT    => '-created,+title,name.with.dots',
+        ParametersParserInterface::PARAM_FILTER  => ['some' => 'filter'],
+        ParametersParserInterface::PARAM_PAGE    => ['size' => 10, 'offset' => 4],
     ];
 
     /**
@@ -410,56 +410,6 @@ public function testTooManyMediaTypesInContentType()
         $checker->check($parameters);
     }
 
-    /**
-     * Test it should throw exception if 'ext' param is set but extensions are not allowed.
-     */
-    public function testExtentionsNotAllowed1()
-    {
-        $checker = new RestrictiveParameterChecker(
-            $this->prepareExceptions(),
-            $this->prepareCodecMatcher(
-                [[self::TYPE, self::SUB_TYPE, [MediaTypeInterface::PARAM_EXT => 'foo']],],
-                [[self::TYPE, self::SUB_TYPE, null],]
-            )
-        );
-
-        $parameters = $this->parser->parse(
-            $this->prepareRequest(
-                self::JSON_API_TYPE . ';' . MediaTypeInterface::PARAM_EXT . '=foo',
-                self::JSON_API_TYPE,
-                $this->requestParams
-            ),
-            $this->prepareExceptions('throwUnsupportedMediaType')
-        );
-
-        $checker->check($parameters);
-    }
-
-    /**
-     * Test it should throw exception if 'ext' param is set but extensions are not allowed.
-     */
-    public function testExtentionsNotAllowed2()
-    {
-        $checker = new RestrictiveParameterChecker(
-            $this->prepareExceptions(),
-            $this->prepareCodecMatcher(
-                [[self::TYPE, self::SUB_TYPE, null],],
-                [[self::TYPE, self::SUB_TYPE, [MediaTypeInterface::PARAM_EXT => 'foo']],]
-            )
-        );
-
-        $parameters = $this->parser->parse(
-            $this->prepareRequest(
-                self::JSON_API_TYPE,
-                self::JSON_API_TYPE . ';' . MediaTypeInterface::PARAM_EXT . '=foo',
-                $this->requestParams
-            ),
-            $this->prepareExceptions('throwNotAcceptable')
-        );
-
-        $checker->check($parameters);
-    }
-
     /**
      * @param string $contentType
      * @param string $accept
@@ -541,14 +491,7 @@ private function getCheckerWithExtensions($exceptionMethod = null)
                     [self::TYPE, self::SUB_TYPE, null],
                     [self::TYPE, self::SUB_TYPE, ['ext' => 'ext1,ext3']],
                 ]
-            ),
-            false,
-            null,
-            null,
-            null,
-            null,
-            null,
-            true
+            )
         );
 
         return $checker;
diff --git a/tests/Schema/FactoryTest.php b/tests/Schema/FactoryTest.php
index faeaf87d..2d3c9828 100644
--- a/tests/Schema/FactoryTest.php
+++ b/tests/Schema/FactoryTest.php
@@ -22,6 +22,7 @@
 use \Neomerx\JsonApi\Schema\SchemaFactory;
 use \Neomerx\JsonApi\Contracts\Schema\LinkInterface;
 use \Neomerx\JsonApi\Contracts\Schema\SchemaFactoryInterface;
+use \Neomerx\JsonApi\Contracts\Schema\SchemaProviderInterface;
 
 /**
  * @package Neomerx\Tests\JsonApi
@@ -55,64 +56,39 @@ public function testCreateContainer()
      */
     public function testCreateResourceObject()
     {
+        $schema = Mockery::mock(SchemaProviderInterface::class);
+        $schema->shouldReceive('getResourceType')->once()->andReturn('some-type');
+
+        /** @var SchemaProviderInterface $schema */
+
         $this->assertNotNull($resource = $this->factory->createResourceObject(
+            $schema,
+            $resource = new stdClass(),
             $isInArray = false,
-            $type = 'people',
-            $idx = '123',
-            $attributes = ['firstName' => 'John', 'lastName' => 'Dow'],
-            $meta = ['some' => 'author meta'],
-            $selfUrl = 'peopleSelfUrl/',
-            $isShowSelf = false,
-            $isShowMeta = false,
-            $isShowSelfInIncluded = true,
-            $isShowLinksInIncluded = true,
-            $isShowMetaInIncluded = true,
-            $isShowMetaInRelShips = true
+            $attributeKeysFilter = ['field1', 'field2']
         ));
 
         $this->assertEquals($isInArray, $resource->isInArray());
-        $this->assertEquals($type, $resource->getType());
-        $this->assertEquals($idx, $resource->getId());
-        $this->assertEquals($attributes, $resource->getAttributes());
-        $this->assertEquals($meta, $resource->getMeta());
-        $this->assertEquals($selfUrl, $resource->getSelfUrl());
-        $this->assertEquals($isShowSelf, $resource->isShowSelf());
-        $this->assertEquals($isShowMeta, $resource->isShowMeta());
-        $this->assertEquals($isShowSelfInIncluded, $resource->isShowSelfInIncluded());
-        $this->assertEquals($isShowLinksInIncluded, $resource->isShowRelationshipsInIncluded());
-        $this->assertEquals($isShowMetaInIncluded, $resource->isShowMetaInIncluded());
-        $this->assertEquals($isShowMetaInRelShips, $resource->isShowMetaInRelationships());
+        $this->assertSame('some-type', $resource->getType());
     }
 
     /**
-     * Test create link object.
+     * Test create relationship object.
      */
-    public function testCreateLinkObject()
+    public function testCreateRelationshipObject()
     {
-        $this->assertNotNull($link = $this->factory->createRelationshipObject(
-            $name = 'link-name',
-            $data = new stdClass(),
-            $selfSubUrl = $this->factory->createLink('selfSubUrl'),
-            $relatedSubUrl = $this->factory->createLink('relatedSubUrl'),
-            $isShowAsRef = false,
-            $isShowSelf = true,
-            $isShowRelated = true,
-            $isShowData = true,
-            $isShowMeta = true,
-            $isShowPagination = true,
-            $pagination = [Mockery::mock(LinkInterface::class)]
+        $this->assertNotNull($relationship = $this->factory->createRelationshipObject(
+            $name  = 'link-name',
+            $data  = new stdClass(),
+            $links = [LinkInterface::SELF => $this->factory->createLink('selfSubUrl')],
+            $meta  = ['some' => 'meta'],
+            $isShowData = true
         ));
 
-        $this->assertEquals($name, $link->getName());
-        $this->assertEquals($data, $link->getData());
-        $this->assertEquals($selfSubUrl, $link->getSelfLink());
-        $this->assertEquals($relatedSubUrl, $link->getRelatedLink());
-        $this->assertEquals($isShowAsRef, $link->isShowAsReference());
-        $this->assertEquals($isShowSelf, $link->isShowSelf());
-        $this->assertEquals($isShowRelated, $link->isShowRelated());
-        $this->assertEquals($isShowData, $link->isShowData());
-        $this->assertEquals($isShowMeta, $link->isShowMeta());
-        $this->assertEquals($isShowPagination, $link->isShowPagination());
-        $this->assertSame($pagination, $link->getPagination());
+        $this->assertEquals($name, $relationship->getName());
+        $this->assertEquals($data, $relationship->getData());
+        $this->assertEquals($links, $relationship->getLinks());
+        $this->assertEquals($meta, $relationship->getMeta());
+        $this->assertEquals($isShowData, $relationship->isShowData());
     }
 }