diff --git a/monikerclient/cli/base.py b/monikerclient/cli/base.py
index cf0fab7f..8ab1a457 100644
--- a/monikerclient/cli/base.py
+++ b/monikerclient/cli/base.py
@@ -76,11 +76,6 @@ class ListCommand(Command, Lister):
 
 
 class GetCommand(Command, ShowOne):
-    def get_parser(self, prog_name):
-        parser = super(GetCommand, self).get_parser(prog_name)
-        parser.add_argument('id', help='The ID or Name to get')
-        return parser
-
     def post_execute(self, results):
         return results.keys(), results.values()
 
diff --git a/monikerclient/cli/domains.py b/monikerclient/cli/domains.py
index 83df27c6..39ff9280 100644
--- a/monikerclient/cli/domains.py
+++ b/monikerclient/cli/domains.py
@@ -33,12 +33,12 @@ class GetDomainCommand(base.GetCommand):
     def get_parser(self, prog_name):
         parser = super(GetDomainCommand, self).get_parser(prog_name)
 
-        parser.add_argument('--domain-id', help="Domain ID", required=True)
+        parser.add_argument('id', help="Domain ID", required=True)
 
         return parser
 
     def execute(self, parsed_args):
-        return self.client.domains.get(parsed_args.domain_id)
+        return self.client.domains.get(parsed_args.id)
 
 
 class CreateDomainCommand(base.CreateCommand):
@@ -47,16 +47,15 @@ class CreateDomainCommand(base.CreateCommand):
     def get_parser(self, prog_name):
         parser = super(CreateDomainCommand, self).get_parser(prog_name)
 
-        parser.add_argument('--domain-name', help="Domain Name", required=True)
-        parser.add_argument('--domain-email', help="Domain Email",
-                            required=True)
+        parser.add_argument('--name', help="Domain Name", required=True)
+        parser.add_argument('--email', help="Domain Email", required=True)
 
         return parser
 
     def execute(self, parsed_args):
         domain = Domain(
-            name=parsed_args.domain_name,
-            email=parsed_args.domain_email
+            name=parsed_args.name,
+            email=parsed_args.email,
         )
 
         return self.client.domains.create(domain)
@@ -68,18 +67,22 @@ class UpdateDomainCommand(base.UpdateCommand):
     def get_parser(self, prog_name):
         parser = super(UpdateDomainCommand, self).get_parser(prog_name)
 
-        parser.add_argument('--domain-id', help="Domain ID", required=True)
-        parser.add_argument('--domain-name', help="Domain Name")
-        parser.add_argument('--domain-email', help="Domain Email")
+        parser.add_argument('id', help="Domain ID", required=True)
+        parser.add_argument('--name', help="Domain Name")
+        parser.add_argument('--email', help="Domain Email")
 
         return parser
 
     def execute(self, parsed_args):
         # TODO: API needs updating.. this get is silly
-        domain = self.client.domains.get(parsed_args.domain_id)
+        domain = self.client.domains.get(parsed_args.id)
 
         # TODO: How do we tell if an arg was supplied or intentionally set to
         #       None?
+        domain.update({
+            'name': parsed_args.name,
+            'email': parsed_args.email,
+        })
 
         return self.client.domains.update(domain)
 
@@ -90,9 +93,9 @@ class DeleteDomainCommand(base.DeleteCommand):
     def get_parser(self, prog_name):
         parser = super(DeleteDomainCommand, self).get_parser(prog_name)
 
-        parser.add_argument('--domain-id', help="Domain ID")
+        parser.add_argument('id', help="Domain ID", required=True)
 
         return parser
 
     def execute(self, parsed_args):
-        return self.client.domains.delete(parsed_args.domain_id)
+        return self.client.domains.delete(parsed_args.id)
diff --git a/monikerclient/cli/records.py b/monikerclient/cli/records.py
new file mode 100644
index 00000000..d3bc41e4
--- /dev/null
+++ b/monikerclient/cli/records.py
@@ -0,0 +1,121 @@
+# Copyright 2012 Managed I.T.
+#
+# Author: Kiall Mac Innes <kiall@managedit.ie>
+#
+# 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.
+import logging
+from monikerclient.cli import base
+from monikerclient.v1.records import Record
+
+LOG = logging.getLogger(__name__)
+
+
+class ListRecordsCommand(base.ListCommand):
+    """ List Records """
+
+    def get_parser(self, prog_name):
+        parser = super(ListRecordsCommand, self).get_parser(prog_name)
+
+        parser.add_argument('domain_id', help="Domain ID")
+
+        return parser
+
+    def execute(self, parsed_args):
+        return self.client.records.list(parsed_args.domain_id)
+
+
+class GetRecordCommand(base.GetCommand):
+    """ Get Record """
+
+    def get_parser(self, prog_name):
+        parser = super(GetRecordCommand, self).get_parser(prog_name)
+
+        parser.add_argument('domain_id', help="Domain ID")
+        parser.add_argument('id', help="Record ID")
+
+        return parser
+
+    def execute(self, parsed_args):
+        return self.client.records.get(parsed_args.domain_id, parsed_args.id)
+
+
+class CreateRecordCommand(base.CreateCommand):
+    """ Create Record """
+
+    def get_parser(self, prog_name):
+        parser = super(CreateRecordCommand, self).get_parser(prog_name)
+
+        parser.add_argument('domain_id', help="Domain ID")
+        parser.add_argument('--name', help="Record Name", required=True)
+        parser.add_argument('--type', help="Record Type", required=True)
+        parser.add_argument('--data', help="Record Data", required=True)
+        parser.add_argument('--ttl', type=int, help="Record TTL")
+
+        return parser
+
+    def execute(self, parsed_args):
+        record = Record(
+            name=parsed_args.name,
+            type=parsed_args.type,
+            data=parsed_args.data,
+            ttl=parsed_args.ttl,
+        )
+
+        return self.client.records.create(parsed_args.domain_id, record)
+
+
+class UpdateRecordCommand(base.UpdateCommand):
+    """ Update Record """
+
+    def get_parser(self, prog_name):
+        parser = super(UpdateRecordCommand, self).get_parser(prog_name)
+
+        parser.add_argument('domain_id', help="Domain ID")
+        parser.add_argument('id', help="Record ID")
+        parser.add_argument('--name', help="Record Name")
+        parser.add_argument('--type', help="Record Type")
+        parser.add_argument('--data', help="Record Data")
+        parser.add_argument('--ttl', type=int, help="Record TTL")
+
+        return parser
+
+    def execute(self, parsed_args):
+        # TODO: API needs updating.. this get is silly
+        record = self.client.records.get(parsed_args.domain_id, parsed_args.id)
+
+        # TODO: How do we tell if an arg was supplied or intentionally set to
+        #       None?
+        record.update({
+            'name': parsed_args.name,
+            'type': parsed_args.type,
+            'data': parsed_args.data,
+            'ttl': parsed_args.ttl,
+        })
+
+        return self.client.records.update(parsed_args.domain_id, record)
+
+
+class DeleteRecordCommand(base.DeleteCommand):
+    """ Delete Record """
+
+    def get_parser(self, prog_name):
+        parser = super(DeleteRecordCommand, self).get_parser(prog_name)
+
+        parser.add_argument('domain_id', help="Domain ID")
+        parser.add_argument('id', help="Record ID")
+
+        return parser
+
+    def execute(self, parsed_args):
+        return self.client.records.delete(parsed_args.domain_id,
+                                          parsed_args.id)
diff --git a/monikerclient/cli/servers.py b/monikerclient/cli/servers.py
new file mode 100644
index 00000000..a399abe1
--- /dev/null
+++ b/monikerclient/cli/servers.py
@@ -0,0 +1,105 @@
+# Copyright 2012 Managed I.T.
+#
+# Author: Kiall Mac Innes <kiall@managedit.ie>
+#
+# 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.
+import logging
+from monikerclient.cli import base
+from monikerclient.v1.servers import Server
+
+LOG = logging.getLogger(__name__)
+
+
+class ListServersCommand(base.ListCommand):
+    """ List Servers """
+
+    def execute(self, parsed_args):
+        return self.client.servers.list()
+
+
+class GetServerCommand(base.GetCommand):
+    """ Get Server """
+
+    def get_parser(self, prog_name):
+        parser = super(GetServerCommand, self).get_parser(prog_name)
+
+        parser.add_argument('id', help="Server ID", required=True)
+
+        return parser
+
+    def execute(self, parsed_args):
+        return self.client.servers.get(parsed_args.id)
+
+
+class CreateServerCommand(base.CreateCommand):
+    """ Create Server """
+
+    def get_parser(self, prog_name):
+        parser = super(CreateServerCommand, self).get_parser(prog_name)
+
+        parser.add_argument('--name', help="Server Name", required=True)
+        parser.add_argument('--ipv4', help="Server IPv4 Address")
+        parser.add_argument('--ipv6', help="Server IPv6 Address")
+
+        return parser
+
+    def execute(self, parsed_args):
+        server = Server(
+            name=parsed_args.name,
+            ipv4=parsed_args.ipv4,
+            ipv6=parsed_args.ipv6,
+        )
+
+        return self.client.servers.create(server)
+
+
+class UpdateServerCommand(base.UpdateCommand):
+    """ Update Server """
+
+    def get_parser(self, prog_name):
+        parser = super(UpdateServerCommand, self).get_parser(prog_name)
+
+        parser.add_argument('id', help="Server ID", required=True)
+        parser.add_argument('--name', help="Server Name")
+        parser.add_argument('--ipv4', help="Server IPv4 Address")
+        parser.add_argument('--ipv6', help="Server IPv6 Address")
+
+        return parser
+
+    def execute(self, parsed_args):
+        # TODO: API needs updating.. this get is silly
+        server = self.client.servers.get(parsed_args.id)
+
+        # TODO: How do we tell if an arg was supplied or intentionally set to
+        #       None?
+        server.update({
+            'name': parsed_args.name,
+            'ipv4': parsed_args.ipv4,
+            'ipv6': parsed_args.ipv6,
+        })
+
+        return self.client.servers.update(server)
+
+
+class DeleteServerCommand(base.DeleteCommand):
+    """ Delete Server """
+
+    def get_parser(self, prog_name):
+        parser = super(DeleteServerCommand, self).get_parser(prog_name)
+
+        parser.add_argument('id', help="Server ID", required=True)
+
+        return parser
+
+    def execute(self, parsed_args):
+        return self.client.servers.delete(parsed_args.id)
diff --git a/monikerclient/resources/schemas/v1/domain.json b/monikerclient/resources/schemas/v1/domain.json
index d41646f9..0eecc9b7 100644
--- a/monikerclient/resources/schemas/v1/domain.json
+++ b/monikerclient/resources/schemas/v1/domain.json
@@ -16,8 +16,8 @@
         "name": {
             "type": "string",
             "description": "Domain name",
+            "format": "host-name",
             "maxLength": 255,
-            "pattern": "^.+[^\\.]$",
             "required": true
         },
         "email": {
@@ -43,10 +43,20 @@
             "readonly": true
         },
         "updated_at": {
-            "type": "string",
+            "type": ["string", "null"],
             "description": "Date and time of image registration",
             "format": "date-time",
             "readonly": true
         }
-    }
+    },
+    "links": [{
+        "rel": "self",
+        "href": "/domains/{id}"
+    }, {
+        "rel": "records",
+        "href": "/domains/{id}/records"
+    }, {
+        "rel": "collection",
+        "href": "/domains"
+    }]
 }
diff --git a/monikerclient/resources/schemas/v1/record.json b/monikerclient/resources/schemas/v1/record.json
index a6b3391e..80a16dc7 100644
--- a/monikerclient/resources/schemas/v1/record.json
+++ b/monikerclient/resources/schemas/v1/record.json
@@ -13,17 +13,24 @@
             "pattern": "^([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}$",
             "readonly": true
         },
+        "domain_id": {
+            "type": "string",
+            "description": "Domain Identifier",
+            "pattern": "^([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}$",
+            "readonly": true
+        },
         "name": {
             "type": "string",
             "description": "DNS Record Name",
+            "format": "host-name",
             "maxLength": 255,
-            "pattern": "^.+[^\\.]$",
             "required": true
         },
         "type": {
             "type": "string",
             "description": "DNS Record Type",
-            "enum": ["A", "AAAA", "CNAME", "MX", "SRV", "TXT", "SPF", "NS"]
+            "enum": ["A", "AAAA", "CNAME", "MX", "SRV", "TXT", "SPF", "NS"],
+            "required": true
         },
         "data": {
             "type": "string",
@@ -31,8 +38,14 @@
             "maxLength": 255,
             "required": true
         },
+        "priority": {
+            "type": ["integer", "null"],
+            "description": "DNS Record Priority",
+            "min": 0,
+            "max": 99999
+        },
         "ttl": {
-            "type": "integer",
+            "type": ["integer", "null"],
             "description": "Time to live",
             "min": 60
         },
@@ -43,10 +56,131 @@
             "readonly": true
         },
         "updated_at": {
-            "type": "string",
+            "type": ["string", "null"],
             "description": "Date and time of image registration",
             "format": "date-time",
             "readonly": true
         }
-    }
+    },
+    "oneOf": [{
+        "description": "An A Record",
+        "properties": {
+            "type": {
+                "type": "string",
+                "enum": ["A"]
+            },
+            "data": {
+                "format": "ip-address",
+                "required": true
+            },
+            "priority": {
+                "type": "null"
+            }
+        }
+    }, {
+        "description": "An AAAA Record",
+        "properties": {
+            "type": {
+                "type": "string",
+                "enum": ["AAAA"]
+            },
+            "data": {
+                "format": "ipv6",
+                "required": true
+            },
+            "priority": {
+                "type": "null"
+            }
+        }
+    }, {
+        "description": "A CNAME Record",
+        "properties": {
+            "type": {
+                "type": "string",
+                "enum": ["CNAME"]
+            },
+            "data": {
+                "format": "host-name",
+                "required": true
+            },
+            "priority": {
+                "type": "null"
+            }
+        }
+    }, {
+        "description": "A MX Record",
+        "properties": {
+            "type": {
+                "type": "string",
+                "enum": ["MX"]
+            },
+            "data": {
+                "format": "host-name",
+                "required": true
+            },
+            "priority": {
+                "type": "integer",
+                "required": true
+            }
+        }
+    }, {
+        "description": "A SRV Record",
+        "properties": {
+            "type": {
+                "type": "string",
+                "enum": ["SRV"]
+            },
+            "priority": {
+                "type": "integer",
+                "required": true
+            }
+        }
+    }, {
+        "description": "A TXT Record",
+        "properties": {
+            "type": {
+                "type": "string",
+                "enum": ["TXT"]
+            },
+            "priority": {
+                "type": "null"
+            }
+        }
+    }, {
+        "description": "A SPF Record",
+        "properties": {
+            "type": {
+                "type": "string",
+                "enum": ["SPF"]
+            },
+            "priority": {
+                "type": "null"
+            }
+        }
+    }, {
+        "description": "A NS Record",
+        "properties": {
+            "type": {
+                "type": "string",
+                "enum": ["NS"]
+            },
+            "data": {
+                "format": "host-name",
+                "required": true
+            },
+            "priority": {
+                "type": "null"
+            }
+        }
+    }],
+    "links": [{
+        "rel": "self",
+        "href": "/domains/{domain_id}/records/{id}"
+    }, {
+        "rel": "domain",
+        "href": "/domains/{domain_id}"
+    }, {
+        "rel": "collection",
+        "href": "/domains/{domain_id}/records"
+    }]
 }
diff --git a/monikerclient/resources/schemas/v1/server.json b/monikerclient/resources/schemas/v1/server.json
index 92470fb9..2af9c781 100644
--- a/monikerclient/resources/schemas/v1/server.json
+++ b/monikerclient/resources/schemas/v1/server.json
@@ -16,15 +16,14 @@
         "name": {
             "type": "string",
             "description": "Server DNS name",
+            "format": "host-name",
             "maxLength": 255,
-            "pattern": "^.+[^\\.]$",
             "required": true
         },
         "ipv4": {
             "type": "string",
             "description": "IPv4 address of server",
-            "format": "ip-address",
-            "required": true
+            "format": "ip-address"
         },
         "ipv6": {
             "type": "string",
@@ -38,10 +37,32 @@
             "readonly": true
         },
         "updated_at": {
-            "type": "string",
+            "type": ["string", "null"],
             "description": "Date and time of last server update",
             "format": "date-time",
             "readonly": true
         }
-    }
+    },
+    "anyOf": [{
+        "description": "IPv4",
+        "properties": {
+            "ipv4": {
+                "required": true
+            }
+        }
+    }, {
+        "description": "IPv6",
+        "properties": {
+            "ipv6": {
+                "required": true
+            }
+        }
+    }],
+    "links": [{
+        "rel": "self",
+        "href": "/servers/{id}"
+    }, {
+        "rel": "collection",
+        "href": "/servers"
+    }]
 }
diff --git a/monikerclient/v1/records.py b/monikerclient/v1/records.py
index 57a4cbba..e42ce898 100644
--- a/monikerclient/v1/records.py
+++ b/monikerclient/v1/records.py
@@ -17,63 +17,97 @@ import json
 from monikerclient import warlock
 from monikerclient import utils
 from monikerclient.v1.base import Controller
+from monikerclient.v1.domains import Domain
 
 
 Record = warlock.model_factory(utils.load_schema('v1', 'record'))
 
 
 class RecordsController(Controller):
-    def list(self):
+    def list(self, domain):
         """
         Retrieve a list of records
 
+        :param domain: :class:`Domain` or Domain Identifier
         :returns: A list of :class:`Record`s
         """
-        response = self.client.get('/records')
+        domain_id = domain.id if isinstance(domain, Domain) else domain
+
+        response = self.client.get('/domains/%(domain_id)s/records' % {
+            'domain_id': domain_id
+        })
 
         return [Record(i) for i in response.json['records']]
 
-    def get(self, record_id):
+    def get(self, domain, record_id):
         """
         Retrieve a record
 
+        :param domain: :class:`Domain` or Domain Identifier
         :param record_id: Record Identifier
         :returns: :class:`Record`
         """
-        response = self.client.get('/records/%s' % record_id)
+        domain_id = domain.id if isinstance(domain, Domain) else domain
+
+        uri = '/domains/%(domain_id)s/records/%(record_id)s' % {
+            'domain_id': domain_id,
+            'record_id': record_id
+        }
+
+        response = self.client.get(uri)
 
         return Record(response.json)
 
-    def create(self, record):
+    def create(self, domain, record):
         """
         Create a record
 
+        :param domain: :class:`Domain` or Domain Identifier
         :param record: A :class:`Record` to create
         :returns: :class:`Record`
         """
-        response = self.client.post('/records', data=json.dumps(record))
+        domain_id = domain.id if isinstance(domain, Domain) else domain
 
-        return record.update(response.json)
+        uri = '/domains/%(domain_id)s/records' % {
+            'domain_id': domain_id
+        }
 
-    def update(self, record):
+        response = self.client.post(uri, data=json.dumps(record))
+
+        return Record(response.json)
+
+    def update(self, domain, record):
         """
         Update a record
 
+        :param domain: :class:`Domain` or Domain Identifier
         :param record: A :class:`Record` to update
         :returns: :class:`Record`
         """
-        response = self.client.put('/records/%s' % record.id,
-                                   data=json.dumps(record))
+        domain_id = domain.id if isinstance(domain, Domain) else domain
+
+        uri = '/domains/%(domain_id)s/records/%(record_id)s' % {
+            'domain_id': domain_id,
+            'record_id': record.id
+        }
+
+        response = self.client.put(uri, data=json.dumps(record))
 
         return record.update(response.json)
 
-    def delete(self, record):
+    def delete(self, domain, record):
         """
         Delete a record
 
+        :param domain: :class:`Domain` or Domain Identifier
         :param record: A :class:`Record`, or Record Identifier to delete
         """
-        if isinstance(record, Record):
-            self.client.delete('/records/%s' % record.id)
-        else:
-            self.client.delete('/records/%s' % record)
+        domain_id = domain.id if isinstance(domain, Domain) else domain
+        record_id = record.id if isinstance(record, Record) else record
+
+        uri = '/domains/%(domain_id)s/records/%(record_id)s' % {
+            'domain_id': domain_id,
+            'record_id': record_id
+        }
+
+        self.client.delete(uri)
diff --git a/monikerclient/v1/servers.py b/monikerclient/v1/servers.py
index 3c98b121..d5b69463 100644
--- a/monikerclient/v1/servers.py
+++ b/monikerclient/v1/servers.py
@@ -53,7 +53,7 @@ class ServersController(Controller):
         """
         response = self.client.post('/servers', data=json.dumps(server))
 
-        return server.update(response.json)
+        return Server(response.json)
 
     def update(self, server):
         """
diff --git a/monikerclient/warlock.py b/monikerclient/warlock.py
index 7fa0616e..ad2bfadf 100644
--- a/monikerclient/warlock.py
+++ b/monikerclient/warlock.py
@@ -16,8 +16,11 @@
 # Hopefully we can upstream the changes ASAP.
 #
 import copy
+import logging
 import jsonschema
 
+LOG = logging.getLogger(__name__)
+
 
 class InvalidOperation(RuntimeError):
     pass
@@ -38,8 +41,8 @@ def model_factory(schema):
         """Apply a JSON schema to an object"""
         try:
             jsonschema.validate(obj, schema)
-        except jsonschema.ValidationError:
-            raise ValidationError()
+        except jsonschema.ValidationError, e:
+            raise ValidationError(str(e))
 
     class Model(dict):
         """Self-validating model for arbitrary objects"""
@@ -51,8 +54,8 @@ def model_factory(schema):
             self.__dict__['validator'] = validator
             try:
                 self.validator(d)
-            except ValidationError:
-                raise ValueError()
+            except ValidationError, e:
+                raise ValueError('Validation Error: %s' % str(e))
             else:
                 dict.__init__(self, d)
 
diff --git a/setup.py b/setup.py
index 1e76ba9d..608cba00 100755
--- a/setup.py
+++ b/setup.py
@@ -54,6 +54,18 @@ setup(
         domain-create = monikerclient.cli.domains:CreateDomainCommand
         domain-update = monikerclient.cli.domains:UpdateDomainCommand
         domain-delete = monikerclient.cli.domains:DeleteDomainCommand
+
+        record-list = monikerclient.cli.records:ListRecordsCommand
+        record-get = monikerclient.cli.records:GetRecordCommand
+        record-create = monikerclient.cli.records:CreateRecordCommand
+        record-update = monikerclient.cli.records:UpdateRecordCommand
+        record-delete = monikerclient.cli.records:DeleteRecordCommand
+
+        server-list = monikerclient.cli.servers:ListServersCommand
+        server-get = monikerclient.cli.servers:GetServerCommand
+        server-create = monikerclient.cli.servers:CreateServerCommand
+        server-update = monikerclient.cli.servers:UpdateServerCommand
+        server-delete = monikerclient.cli.servers:DeleteServerCommand
         """),
     classifiers=[
         'Development Status :: 3 - Alpha',