diff --git a/.travis.yml b/.travis.yml
index a7803a70da..fead52f55d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,15 +1,24 @@
 language: python
 python:
-  - "2.6"
-  - "2.7"
-  - "3.2"
-  - "3.3"
-  - "3.4"
+  - '2.6'
+  - '2.7'
+  - '3.2'
+  - '3.3'
+  - '3.4'
+  - '3.5'
 install:
-  - pip install . --use-mirrors
-  - pip install -r requirements.txt --use-mirrors
-  - pip install -r tests/requirements.txt --use-mirrors
-script: 
+  - pip install .
+  - pip install -r requirements.txt
+  - pip install -r tests/requirements.txt
+script:
   - flake8 --ignore=F401 twilio
   - flake8 --ignore=E123,E126,E128,E501 tests
   - nosetests
+sudo: false
+notifications:
+  slack:
+    on_success: change
+    on_failure: change
+    rooms:
+      - secure: TcDlBcKXtqmMdVsa3Lsofdqc1uVjqhZouwNETC260rByRb74gTHGZ1Da7PRkv+AZIFUq7S1uWTZXTXJTm154hi4aQb9SE2UowVwTJMjIKyy4P79s1eoyADP92cFEcpqSs4iVpU3t5srTj8cg2fVks8EsV5pDVJut1oBH4qYJEZw=
+      - secure: qqtcwaS0y5e2SVm5g/zSRMgo7FcZ8Oa44bxQUDvJh/84/DHMD3zZoAv/A4+Vlbs0tCjnSKxMDuLxTzpiPgru4KPH7yj4fEXf7+sfwiLD//WBVWpGMYLa+PNCGS6hhnAuFkA2psZCmmzkbJbX0N03EdWiWFzV79WPgNI+WzpYIVQ=
diff --git a/CHANGES.md b/CHANGES.md
index 11b8e7dd71..1d9f2d150b 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -3,6 +3,114 @@ twilio-python Changelog
 
 Here you can see the full list of changes between each twilio-python release.
 
+Version 5.6.0
+-------------
+
+Released September 19, 2016:
+
+- Add Video Grant
+
+Version 5.5.0
+-------------
+
+Released September 1, 2016:
+
+- Add Voice Grant
+
+Version 5.4.0
+-------------
+
+Released February 29, 2016:
+
+- Add support for API Key auth
+
+Version 5.3.0
+-------------
+
+Released January 28, 2016:
+
+- Add support for filter_friendly_name in WorkflowConfig
+- Load reservations by default in TaskRouter
+
+Version 5.2.0
+-------------
+
+Released December 17, 2015:
+
+- Add support for IP Messaging
+
+Version 5.1.0
+-------------
+
+Released December 11, 2015:
+
+- Remove pyjwt dependency
+
+Version 5.0.0
+-------------
+
+Released December 8, 2015:
+
+- Update Access Tokens so that NBF is a optional parameter
+
+Version 4.10.0
+-------------
+
+Released December 3, 2015:
+
+- Add Access Tokens
+
+Version 4.9.2
+-------------
+
+Released November 25, 2015:
+
+- Fix for SIP Trunking bug
+
+Version 4.9.1
+-------------
+
+Released November 18, 2015:
+
+- Addresses bug with SMS Pricing country
+
+Version 4.9.0
+-------------
+
+Released November 3, 2015:
+
+- Add support for SIP Trunking
+
+Version 4.8.0
+-------------
+
+- Add support for SMS pricing
+
+
+Version 4.6.0
+-------------
+
+- Add /Keys endpoint
+
+Version 4.6.0
+-------------
+
+Released September 23, 2015:
+
+- Allow fetching TaskRouter reservations by Worker
+- Add missing Enqueue->Task TwiML generation
+- Add Worflow construction
+
+Version 4.5.0
+-------------
+
+Released August 11, 2015:
+
+- Add support for new Taskrouter JWT Functionality, JWTs now grant access to
+  - Workspace
+  - Worker
+  - TaskQueue
+
 Version 4.4.0
 -------------
 
diff --git a/Makefile b/Makefile
index 9f51e8826a..5b717bda7a 100644
--- a/Makefile
+++ b/Makefile
@@ -1,10 +1,10 @@
-.PHONY: clean venv install analysis test test-install docs docs-install
+.PHONY: clean install analysis test test-install docs docs-install
 
 venv:
 	virtualenv venv
 
 install: venv
-	. venv/bin/activate; pip install . --use-mirrors
+	. venv/bin/activate; pip install .
 
 test-install: install
 	. venv/bin/activate; pip install -r tests/requirements.txt
diff --git a/docs/appengine.rst b/docs/appengine.rst
index 0cdf9db9c8..74a26a4fcd 100644
--- a/docs/appengine.rst
+++ b/docs/appengine.rst
@@ -102,7 +102,7 @@ The key is to lay out your project in a way that makes sense.
     the settings you want in Google App Engine - Note the folder path ends with
     ``twilio-demo/src``.
 
-    .. image:: https://www.evernote.com/shard/s265/sh/1b9407b0-c89b-464d-b352-dbf8fc7a7f41/f536b8e79747f43220fc12e0e0026ee2/res/5b2f83af-8a7f-451f-afba-db092c55aa44/skitch.png
+    .. image:: http://howtodocs.s3.amazonaws.com/helpers/appengine.png
 
     Once App Engine is running locally, in your browser, you should be able to
     navigate to ``http://localhost`` + the provided Port and view the twilio
diff --git a/docs/usage/taskrouter.rst b/docs/usage/taskrouter.rst
index 5915a4744e..76de468ed3 100644
--- a/docs/usage/taskrouter.rst
+++ b/docs/usage/taskrouter.rst
@@ -38,6 +38,71 @@ its unique ID.
     )
     print workspace.sid
 
+..
+
+The following code will get an instance of an existing :class:`workspace` resource
+
+.. code-block:: python
+
+    from twilio.rest import TwilioTaskRouterClient
+
+    # To find these visit https://www.twilio.com/user/account
+    ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX"
+    AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY"
+
+    client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN)
+    workspace = client.workspaces.get(WORKSPACE_SID)
+    print workspace.friendly_name
+..
+
+The following code will get the list of all existing :class:`workspace` resources
+
+.. code-block:: python
+
+    from twilio.rest import TwilioTaskRouterClient
+
+    # To find these visit https://www.twilio.com/user/account
+    ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX"
+    AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY"
+
+    client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN)
+    for workspace in  client.workspaces.list()
+        print workspace.friendly_name
+..
+
+The following code will create a update an existing :class:`Workspace` resource
+
+.. code-block:: python
+
+    from twilio.rest import TwilioTaskRouterClient
+
+    # To find these visit https://www.twilio.com/user/account
+    ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX"
+    AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY"
+
+    client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN)
+    workspace = client.workspaces.update(
+        WORKSPACE_SID,
+        friendly_name='Test Workspace',
+        event_callback_uri="http://www.example.com",
+        template='FIFO')
+
+..
+The following code will delete an existing :class:`workspace` resource
+
+.. code-block:: python
+
+    from twilio.rest import TwilioTaskRouterClient
+
+    # To find these visit https://www.twilio.com/user/account
+    ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX"
+    AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY"
+
+    client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN)
+    client.workspaces.delete(WORKSPACE_SID)
+..
+
+
 
 Workflows
 ---------
@@ -87,13 +152,139 @@ unique ID:
 
     client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN)
 
-    workspace = client.workflows(WORKSPACE_SID).create(
+    workflow = client.workflows(WORKSPACE_SID).create(
         friendly_name="Incoming Call Flow",
         assignment_callback_url="https://example.com/callback",
         fallback_assignment_callback_url="https://example.com/callback2",
         configuration=CONFIG
     )
-    print workspace.sid
+    print workflow.sid
+
+..
+
+The following code will get a instance of an existing :class:`workflow` resource
+
+.. code-block:: python
+
+    from twilio.rest import TwilioTaskRouterClient
+
+    # To find these visit https://www.twilio.com/user/account
+    ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX"
+    AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY"
+
+    # See previous examples to create a Workspace
+    WORKSPACE_SID = "WSZZZZZZZZZZZZZZ"
+
+    client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN)
+
+    workflow = client.workflows(WORKSPACE_SID).get(WORKFLOW_SID)
+    print workflow.friendly_name
+
+..
+
+
+
+The following code will get a list of all existing :class:`workflow` resources
+
+.. code-block:: python
+
+    from twilio.rest import TwilioTaskRouterClient
+
+    # To find these visit https://www.twilio.com/user/account
+    ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX"
+    AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY"
+
+    # See previous examples to create a Workspace
+    WORKSPACE_SID = "WSZZZZZZZZZZZZZZ"
+
+    client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN)
+
+    for workflow in client.workflows(WORKSPACE_SID).list()
+        print workflow.friendly_name
+
+..
+
+The following code will update an existing :class:`workflow` resource
+
+.. code-block:: python
+
+    from twilio.rest import TwilioTaskRouterClient
+
+    # To find these visit https://www.twilio.com/user/account
+    ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX"
+    AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY"
+
+    # See previous examples to create a Workspace
+    WORKSPACE_SID = "WSZZZZZZZZZZZZZZ"
+
+    # Some JSON to configure the Workflow. See the documentation at
+    # http://www.twilio.com/docs/taskrouter for more details.
+    CONFIG = """
+    {
+       "task_routing":{
+          "filters":[
+             {
+                "friendly_name":"Gold Tickets",
+                "expression":"customer_value == 'Gold' AND type == 'ticket'",
+                "targets":[
+                   {
+                      "task_queue_sid":"WQ0123456789abcdef0123456789abcdef",
+                      "priority":"2"
+                   }
+                ]
+             },
+             {
+                "targets": [
+                    {
+                        "queue": "WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f",
+                        "priority": "1"
+                    }
+                ],
+                "friendly_name": "Marketing",
+                "expression": "type == 'marketing'"
+            }
+          ],
+          "default_filter":{
+             "task_queue_sid":"WQabcdef01234567890123456789abcdef"
+          }
+       }
+    }
+    """
+
+    client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN)
+
+    workflow = client.workflows(WORKSPACE_SID).update(
+        WORKFLOW_SID,
+        friendly_name="Incoming Call Flow",
+        assignment_callback_url="https://example.com/callback",
+        fallback_assignment_callback_url="https://example.com/callback2",
+        configuration=CONFIG
+    )
+    print workflow.sid
+
+..
+
+The following code will delete an existing :class:`Workflow`
+
+.. code-block:: python
+
+    from twilio.rest import TwilioTaskRouterClient
+
+    # To find these visit https://www.twilio.com/user/account
+    ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX"
+    AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY"
+
+    # See previous examples to create a Workspace
+    WORKSPACE_SID = "WSZZZZZZZZZZZZZZ"
+
+    client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN)
+
+    client.workflows(WORKSPACE_SID).delete(
+        WORKFLOW_SID
+    )
+
+
+..
 
 
 Activities
@@ -122,6 +313,87 @@ To create a new :class:`Activity`:
         available=False,  # Whether workers are available to handle tasks during this activity
     )
 
+..
+
+To get an existing :class:`activity` resource
+
+.. code-block:: python
+
+    from twilio.rest import TwilioTaskRouterClient
+
+    # To find these visit https://www.twilio.com/user/account
+    ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX"
+    AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY"
+
+    # See previous examples to create a Workspace
+    WORKSPACE_SID = "WSZZZZZZZZZZZZZZ"
+
+    client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN)
+    activity = client.activities(WORKSPACE_SID).get(ACTIVITY_SID)
+    print activity.friendly_name
+
+..
+
+To get a list of existing :class:`activity` resources
+
+.. code-block:: python
+
+    from twilio.rest import TwilioTaskRouterClient
+
+    # To find these visit https://www.twilio.com/user/account
+    ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX"
+    AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY"
+
+    # See previous examples to create a Workspace
+    WORKSPACE_SID = "WSZZZZZZZZZZZZZZ"
+
+    client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN)
+    for activity in client.activities(WORKSPACE_SID).list()
+        print activity.friendly_name
+
+..
+
+To update an existing :class:`Activity`
+
+.. code-block:: python
+
+    from twilio.rest import TwilioTaskRouterClient
+
+    # To find these visit https://www.twilio.com/user/account
+    ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX"
+    AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY"
+
+    # See previous examples to create a Workspace
+    WORKSPACE_SID = "WSZZZZZZZZZZZZZZ"
+
+    client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN)
+    activity = client.activities(WORKSPACE_SID).update(
+        ACTIVITY_SID,
+        friendly_name="Coffee Break",
+        available=True,
+    )
+
+..
+
+To delete an existing :class:`Activity`
+
+.. code-block:: python
+
+    from twilio.rest import TwilioTaskRouterClient
+
+    # To find these visit https://www.twilio.com/user/account
+    ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX"
+    AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY"
+
+    # See previous examples to create a Workspace
+    WORKSPACE_SID = "WSZZZZZZZZZZZZZZ"
+
+    client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN)
+    activity = client.activities(WORKSPACE_SID).delete(
+        ACTIVITY_SID
+    )
+
+..
 
 Workers
 -------
@@ -153,6 +425,92 @@ To create a new :class:`Worker`:
     )
     print worker.sid
 
+..
+
+To get an existing :class:`worker` instance
+
+.. code-block:: python
+
+    from twilio.rest import TwilioTaskRouterClient
+
+    # To find these visit https://www.twilio.com/user/account
+    ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX"
+    AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY"
+
+    # See previous examples to create a Workspace
+    WORKSPACE_SID = "WSZZZZZZZZZZZZZZ"
+
+    client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN)
+    worker = client.workers(WORKSPACE_SID).get(WORKER_SID)
+    print worker_friendly_name;
+..
+
+
+To get an existing :class:`worker` list
+
+.. code-block:: python
+
+    from twilio.rest import TwilioTaskRouterClient
+
+    # To find these visit https://www.twilio.com/user/account
+    ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX"
+    AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY"
+
+    # See previous examples to create a Workspace
+    WORKSPACE_SID = "WSZZZZZZZZZZZZZZ"
+
+    client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN)
+    for worker in client.workers(WORKSPACE_SID).list()
+        print worker_friendly_name;
+..
+
+
+To update an existing :class:`Worker`
+
+.. code-block:: python
+
+    from twilio.rest import TwilioTaskRouterClient
+
+    # To find these visit https://www.twilio.com/user/account
+    ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX"
+    AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY"
+
+    # See previous examples to create a Workspace
+    WORKSPACE_SID = "WSZZZZZZZZZZZZZZ"
+
+    client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN)
+    worker = client.workers(WORKSPACE_SID).update(
+        WORKER_SID,
+        friendly_name="Jamie Howe",
+        attributes="""{
+        "phone": "+14155551234",
+        "languages": ["EN", "ES","DE"]
+    }
+    """
+    )
+    print worker.sid
+
+..
+
+To delete an existing :class:`Worker`
+
+.. code-block:: python
+
+    from twilio.rest import TwilioTaskRouterClient
+
+    # To find these visit https://www.twilio.com/user/account
+    ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX"
+    AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY"
+
+    # See previous examples to create a Workspace
+    WORKSPACE_SID = "WSZZZZZZZZZZZZZZ"
+
+    client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN)
+    client.workers(WORKSPACE_SID).delete(
+        WORKER_SID
+    )
+
+..
 
 TaskQueues
 ----------
@@ -186,6 +544,100 @@ To create a new :class:`TaskQueue`:
     )
     print queue.sid
 
+..
+
+To get an existing :class`TaskQueue` instance
+
+.. code-block:: python
+
+    from twilio.rest import TwilioTaskRouterClient
+
+    # To find these visit https://www.twilio.com/user/account
+    ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX"
+    AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY"
+
+    # See previous examples to create a Workspace
+    WORKSPACE_SID = "WSZZZZZZZZZZZZZZ"
+
+    client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN)
+
+    queue = client.task_queues(WORKSPACE_SID).get(TASKQUEUE_SID)
+    print queue.sid
+
+..
+
+
+
+To get an existing :class`TaskQueue` list
+
+.. code-block:: python
+
+    from twilio.rest import TwilioTaskRouterClient
+
+    # To find these visit https://www.twilio.com/user/account
+    ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX"
+    AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY"
+
+    # See previous examples to create a Workspace
+    WORKSPACE_SID = "WSZZZZZZZZZZZZZZ"
+
+    client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN)
+
+    for queue in client.task_queues(WORKSPACE_SID).list()
+        print queue.sid
+
+..
+
+
+To update an existing :class:`TaskQueue`
+
+.. code-block:: python
+
+    from twilio.rest import TwilioTaskRouterClient
+
+    # To find these visit https://www.twilio.com/user/account
+    ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX"
+    AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY"
+
+    # See previous examples to create a Workspace
+    WORKSPACE_SID = "WSZZZZZZZZZZZZZZ"
+
+    client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN)
+
+    queue = client.task_queues(WORKSPACE_SID).update(
+        TASKQUEUE_SID,
+        friendly_name="Sales+Pre-Sales",
+        # The Activity to assign workers when a task is reserved for them
+        reservation_activity_sid="WA11111111111",
+        # The Activity to assign workers when a task is assigned to them
+        assignment_activity_sid="WA222222222222",
+    )
+    print queue.sid
+
+..
+
+To delete an existing :class:`TaskQueue`
+
+.. code-block:: python
+
+    from twilio.rest import TwilioTaskRouterClient
+
+    # To find these visit https://www.twilio.com/user/account
+    ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX"
+    AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY"
+
+    # See previous examples to create a Workspace
+    WORKSPACE_SID = "WSZZZZZZZZZZZZZZ"
+
+    client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN)
+
+    queue = client.task_queues(WORKSPACE_SID).delete(
+        TASKQUEUE_SID
+    )
+    print queue.sid
+
+..
+
 
 Tasks
 -----
@@ -223,3 +675,161 @@ To create a new :class:`Task` via the REST API:
         workflow_sid=WORKFLOW_SID
     )
     print task.sid
+..
+
+To get an existing :class:`Task` instance
+
+.. code-block:: python
+
+    from twilio.rest import TwilioTaskRouterClient
+
+    # To find these visit https://www.twilio.com/user/account
+    ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX"
+    AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY"
+
+    # See previous examples to create a Workspace
+    WORKSPACE_SID = "WSZZZZZZZZZZZZZZ"
+    WORKFLOW_SID = "WWXXXXXXXXXXXXXX"
+    # Some JSON containing attributes for this task. User-defined.
+    TASK_ATTRIBUTES = """{
+         "type": "call",
+         "contact": "+2014068777",
+         "customer-value": "gold",
+         "task-reason": "support",
+         "callSid": "CA42ed11..."
+    }"""
+
+
+    client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN)
+    task = client.tasks(WORKSPACE_SID).delete(TASK_SID)
+    print task.attributes
+..
+
+
+To get an existing :class:`Task` list
+
+.. code-block:: python
+
+    from twilio.rest import TwilioTaskRouterClient
+
+    # To find these visit https://www.twilio.com/user/account
+    ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX"
+    AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY"
+
+    # See previous examples to create a Workspace
+    WORKSPACE_SID = "WSZZZZZZZZZZZZZZ"
+    WORKFLOW_SID = "WWXXXXXXXXXXXXXX"
+    # Some JSON containing attributes for this task. User-defined.
+    TASK_ATTRIBUTES = """{
+         "type": "call",
+         "contact": "+2014068777",
+         "customer-value": "gold",
+         "task-reason": "support",
+         "callSid": "CA42ed11..."
+    }"""
+
+
+    client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN)
+    for task in client.tasks(WORKSPACE_SID).list()
+        print task.attributes
+..
+
+To update an existing  :class:`Task`
+
+.. code-block:: python
+
+    from twilio.rest import TwilioTaskRouterClient
+
+    # To find these visit https://www.twilio.com/user/account
+    ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX"
+    AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY"
+
+    # See previous examples to create a Workspace
+    WORKSPACE_SID = "WSZZZZZZZZZZZZZZ"
+    WORKFLOW_SID = "WWXXXXXXXXXXXXXX"
+    # Some JSON containing attributes for this task. User-defined.
+    TASK_ATTRIBUTES = """{
+         "type": "call",
+         "contact": "+2014068777",
+         "customer-value": "gold",
+         "task-reason": "support",
+         "callSid": "CA42ed11..."
+    }"""
+
+
+    client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN)
+    task = client.tasks(WORKSPACE_SID).update(
+        TASK_SID,
+        attributes=TASK_ATTRIBUTES,
+        assignment_status='pending',
+        workflow_sid=WORKFLOW_SID
+    )
+    print task.sid
+..
+
+To delete an existing :class:`Task`
+
+.. code-block:: python
+
+    from twilio.rest import TwilioTaskRouterClient
+
+    # To find these visit https://www.twilio.com/user/account
+    ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX"
+    AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY"
+
+    # See previous examples to create a Workspace
+    WORKSPACE_SID = "WSZZZZZZZZZZZZZZ"
+    WORKFLOW_SID = "WWXXXXXXXXXXXXXX"
+    # Some JSON containing attributes for this task. User-defined.
+    TASK_ATTRIBUTES = """{
+         "type": "call",
+         "contact": "+2014068777",
+         "customer-value": "gold",
+         "task-reason": "support",
+         "callSid": "CA42ed11..."
+    }"""
+
+
+    client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN)
+    client.tasks(WORKSPACE_SID).delete(
+        TASK_SID
+    )
+
+..
+
+
+Using Workflow builder helper classes to create a :class:`Workflow` resource.
+
+.. code-block:: python
+
+   from twilio.rest import TwilioTaskRouterClient
+
+    # To find these visit https://www.twilio.com/user/account
+    ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX"
+    AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY"
+
+    # See previous examples to create a Workspace
+    WORKSPACE_SID = "WSZZZZZZZZZZZZZZ"
+
+    rules = [
+         WorkflowRule("1==1", [WorkflowRuleTarget("WQeae4fc2f4db7f377c5d3758fb08b79b7", "1==1", 1, 20)],"SomeQ"),
+         WorkflowRule("1==1", [WorkflowRuleTarget("WQ19ebe92fb33522f018b5a31d805d94da", "1==1", 1, 210)], "SomeOtherQ")
+    ]
+    default_target = WorkflowRuleTarget("WQ9963154bf3122d0a0558f3763951d916", "1==1", None, None)
+    config = WorkflowConfig(rules, default_target)
+    print config.to_json()
+
+    client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN)
+
+    workflow = client.workflows(WORKSPACE_SID).create(
+        friendly_name= "Incoming Call Flow",
+        assignment_callback_url= "https://example.com/callback",
+        fallback_assignment_callback_url= "https://example.com/callback2",
+        configuration= config.to_json()
+    )
+
+    print workflow.sid
+
+
+
+..
\ No newline at end of file
diff --git a/docs/usage/twiml.rst b/docs/usage/twiml.rst
index 6305f16c05..7ae32225ae 100644
--- a/docs/usage/twiml.rst
+++ b/docs/usage/twiml.rst
@@ -30,7 +30,7 @@ The output is perfectly formatted XML:
 .. code-block:: xml
 
    <?xml version="1.0" encoding="utf-8"?>
-   <Response><Say>Hello</Say><Response>
+   <Response><Say>Hello</Say></Response>
 
 The verb methods (outlined in the :doc:`complete reference </api/twiml>`)
 take the body (only text) of the verb as the first argument.
@@ -49,7 +49,7 @@ All attributes are keyword arguments.
     <?xml version="1.0" encoding="utf-8"?>
     <Response>
         <Play loop="3">https://api.twilio.com/cowbell.mp3</Play>
-    <Response>
+    </Response>
 
 The Message and Sms verbs are slightly special: because :const:`from` is a
 Python keyword, use the :const:`sender` parameter to specify the number
@@ -68,7 +68,7 @@ the message should come from:
     <?xml version="1.0" encoding="utf-8"?>
     <Response>
         <Message from="+14155551234">Hello MMS Monkey!</Message>
-    <Response>
+    </Response>
 
 Python 2.6+ added the :const:`with` statement for context management.
 Using :const:`with`, the module can *almost* emulate Ruby blocks.
diff --git a/issue_template.md b/issue_template.md
new file mode 100644
index 0000000000..c041708d80
--- /dev/null
+++ b/issue_template.md
@@ -0,0 +1,25 @@
+*Note: These issues are for bugs and feature requests for the helper libraries. If you need help or support, please email help@twilio.com and one of our experts will assist you!*
+
+
+**Version:**
+**API Subdomain (api/taskrouter/ip_messaging):**
+
+### Code Snippet
+```python
+# paste code here
+```
+
+### Exception / Log
+```
+<place exception / log here>
+```
+
+### Steps to Reproduce
+1.
+2.
+3.
+
+
+### Feature Request
+_If this is a feature request, make sure you search Issues for an existing request before creating a new one!_
+
diff --git a/setup.py b/setup.py
index 78f31fc2ae..69259aa554 100755
--- a/setup.py
+++ b/setup.py
@@ -34,6 +34,7 @@
         ':python_version=="3.2"': ['pysocks'],
         ':python_version=="3.3"': ['pysocks'],
         ':python_version=="3.4"': ['pysocks'],
+        ':python_version=="3.5"': ['pysocks'],
     },
     packages = find_packages(),
     include_package_data=True,
@@ -49,6 +50,7 @@
         "Programming Language :: Python :: 3.2",
         "Programming Language :: Python :: 3.3",
         "Programming Language :: Python :: 3.4",
+        "Programming Language :: Python :: 3.5",
         "Topic :: Software Development :: Libraries :: Python Modules",
         "Topic :: Communications :: Telephony",
         ],
diff --git a/tests/ip_messaging/__init__.py b/tests/ip_messaging/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/ip_messaging/test_channels.py b/tests/ip_messaging/test_channels.py
new file mode 100644
index 0000000000..90720cdd3a
--- /dev/null
+++ b/tests/ip_messaging/test_channels.py
@@ -0,0 +1,54 @@
+import unittest
+from mock import patch, Mock
+from twilio.rest.resources.ip_messaging import Channels, Channel
+from tests.tools import create_mock_json
+
+BASE_URI = "https://ip-messaging.twilio.com/v1/Services/ISxxx"
+ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+AUTH = (ACCOUNT_SID, "token")
+CHANNEL_SID = "CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+
+list_resource = Channels(BASE_URI, AUTH)
+
+
+class ChannelTest(unittest.TestCase):
+
+    @patch("twilio.rest.resources.base.make_twilio_request")
+    def test_create_channel(self, mock):
+        resp = create_mock_json("tests/resources/ip_messaging/channel_instance.json")
+        resp.status_code = 201
+        mock.return_value = resp
+
+        uri = "%s/Channels" % (BASE_URI)
+        list_resource.create(friendly_name='TestChannel', unique_name='Unique')
+        exp_params = {
+            'FriendlyName': "TestChannel",
+            'UniqueName': 'Unique'
+        }
+
+        mock.assert_called_with("POST", uri, data=exp_params, auth=AUTH,
+                                use_json_extension=False)
+
+    @patch("twilio.rest.resources.base.make_twilio_request")
+    def test_get(self, mock):
+        resp = create_mock_json("tests/resources/ip_messaging/channel_instance.json")
+        mock.return_value = resp
+
+        uri = "%s/Channels/%s" % (BASE_URI, CHANNEL_SID)
+        list_resource.get(CHANNEL_SID)
+
+        mock.assert_called_with("GET", uri, auth=AUTH,
+                                use_json_extension=False)
+
+    @patch("twilio.rest.resources.base.Resource.request")
+    def test_delete(self, req):
+        """ Deleting a call should work """
+        resp = Mock()
+        resp.content = ""
+        resp.status_code = 204
+        req.return_value = resp, {}
+
+        app = Channel(list_resource, "CH123")
+        app.delete()
+        uri = "%s/Channels/CH123" % (BASE_URI)
+        req.assert_called_with("DELETE", uri)
diff --git a/tests/ip_messaging/test_credentials.py b/tests/ip_messaging/test_credentials.py
new file mode 100644
index 0000000000..ae749db875
--- /dev/null
+++ b/tests/ip_messaging/test_credentials.py
@@ -0,0 +1,53 @@
+import unittest
+from mock import patch, Mock
+from twilio.rest.resources.ip_messaging import Credentials, Credential
+from tests.tools import create_mock_json
+
+BASE_URI = "https://ip-messaging.twilio.com/v1"
+ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+AUTH = (ACCOUNT_SID, "token")
+CREDENTIAL_SID = "ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+
+list_resource = Credentials(BASE_URI, AUTH)
+
+
+class CredentialTest(unittest.TestCase):
+
+    @patch("twilio.rest.resources.base.make_twilio_request")
+    def test_create_credential(self, mock):
+        resp = create_mock_json("tests/resources/ip_messaging/credential_instance.json")
+        resp.status_code = 201
+        mock.return_value = resp
+
+        uri = "%s/Credentials" % (BASE_URI)
+        list_resource.create('apn')
+        exp_params = {
+            'Type': "apn"
+        }
+
+        mock.assert_called_with("POST", uri, data=exp_params, auth=AUTH,
+                                use_json_extension=False)
+
+    @patch("twilio.rest.resources.base.make_twilio_request")
+    def test_get(self, mock):
+        resp = create_mock_json("tests/resources/ip_messaging/credential_instance.json")
+        mock.return_value = resp
+
+        uri = "%s/Credentials/%s" % (BASE_URI, CREDENTIAL_SID)
+        list_resource.get(CREDENTIAL_SID)
+
+        mock.assert_called_with("GET", uri, auth=AUTH,
+                                use_json_extension=False)
+
+    @patch("twilio.rest.resources.base.Resource.request")
+    def test_delete(self, req):
+        """ Deleting a call should work """
+        resp = Mock()
+        resp.content = ""
+        resp.status_code = 204
+        req.return_value = resp, {}
+
+        app = Credential(list_resource, "IS123")
+        app.delete()
+        uri = "https://ip-messaging.twilio.com/v1/Credentials/IS123"
+        req.assert_called_with("DELETE", uri)
diff --git a/tests/ip_messaging/test_members.py b/tests/ip_messaging/test_members.py
new file mode 100644
index 0000000000..23010bbee3
--- /dev/null
+++ b/tests/ip_messaging/test_members.py
@@ -0,0 +1,53 @@
+import unittest
+from mock import patch, Mock
+from twilio.rest.resources.ip_messaging import Members, Member
+from tests.tools import create_mock_json
+
+BASE_URI = "https://ip-messaging.twilio.com/v1/Services/ISxxx/Channels/CHxxx"
+ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+AUTH = (ACCOUNT_SID, "token")
+MEMBER_SID = "MBaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+
+list_resource = Members(BASE_URI, AUTH)
+
+
+class MemberTest(unittest.TestCase):
+
+    @patch("twilio.rest.resources.base.make_twilio_request")
+    def test_create_member(self, mock):
+        resp = create_mock_json("tests/resources/ip_messaging/member_instance.json")
+        resp.status_code = 201
+        mock.return_value = resp
+
+        uri = "%s/Members" % (BASE_URI)
+        list_resource.create('test_identity')
+        exp_params = {
+            'Identity': "test_identity"
+        }
+
+        mock.assert_called_with("POST", uri, data=exp_params, auth=AUTH,
+                                use_json_extension=False)
+
+    @patch("twilio.rest.resources.base.make_twilio_request")
+    def test_get(self, mock):
+        resp = create_mock_json("tests/resources/ip_messaging/member_instance.json")
+        mock.return_value = resp
+
+        uri = "%s/Members/%s" % (BASE_URI, MEMBER_SID)
+        list_resource.get(MEMBER_SID)
+
+        mock.assert_called_with("GET", uri, auth=AUTH,
+                                use_json_extension=False)
+
+    @patch("twilio.rest.resources.base.Resource.request")
+    def test_delete(self, req):
+        """ Deleting a call should work """
+        resp = Mock()
+        resp.content = ""
+        resp.status_code = 204
+        req.return_value = resp, {}
+
+        app = Member(list_resource, "MB123")
+        app.delete()
+        uri = "%s/Members/MB123" % (BASE_URI)
+        req.assert_called_with("DELETE", uri)
diff --git a/tests/ip_messaging/test_messages.py b/tests/ip_messaging/test_messages.py
new file mode 100644
index 0000000000..49b5778225
--- /dev/null
+++ b/tests/ip_messaging/test_messages.py
@@ -0,0 +1,68 @@
+import unittest
+from mock import patch, Mock
+from twilio.rest.resources.ip_messaging import Messages, Message
+from tests.tools import create_mock_json
+
+BASE_URI = "https://ip-messaging.twilio.com/v1/Services/ISxxx/Channels/CHxxx"
+ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+AUTH = (ACCOUNT_SID, "token")
+MESSAGE_SID = "MSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+
+list_resource = Messages(BASE_URI, AUTH)
+
+
+class MessageTest(unittest.TestCase):
+
+    @patch("twilio.rest.resources.base.make_twilio_request")
+    def test_create_message(self, mock):
+        resp = create_mock_json("tests/resources/ip_messaging/message_instance.json")
+        resp.status_code = 201
+        mock.return_value = resp
+
+        uri = "%s/Messages" % (BASE_URI)
+        list_resource.create('TestBody')
+        exp_params = {
+            'Body': "TestBody"
+        }
+
+        mock.assert_called_with("POST", uri, data=exp_params, auth=AUTH,
+                                use_json_extension=False)
+
+    @patch("twilio.rest.resources.base.make_twilio_request")
+    def test_get(self, mock):
+        resp = create_mock_json("tests/resources/ip_messaging/message_instance.json")
+        mock.return_value = resp
+
+        uri = "%s/Messages/%s" % (BASE_URI, MESSAGE_SID)
+        list_resource.get(MESSAGE_SID)
+
+        mock.assert_called_with("GET", uri, auth=AUTH,
+                                use_json_extension=False)
+
+    @patch("twilio.rest.resources.base.make_twilio_request")
+    def test_update(self, mock):
+        resp = create_mock_json("tests/resources/ip_messaging/message_instance.json")
+        mock.return_value = resp
+
+        update_params = {
+            'UniqueName': 'unique'
+        }
+
+        uri = "%s/Messages/%s" % (BASE_URI, MESSAGE_SID)
+        list_resource.update(MESSAGE_SID, unique_name='unique')
+
+        mock.assert_called_with("POST", uri, data=update_params, auth=AUTH,
+                                use_json_extension=False)
+
+    @patch("twilio.rest.resources.base.Resource.request")
+    def test_delete(self, req):
+        """ Deleting a call should work """
+        resp = Mock()
+        resp.content = ""
+        resp.status_code = 204
+        req.return_value = resp, {}
+
+        app = Message(list_resource, "MS123")
+        app.delete()
+        uri = "%s/Messages/MS123" % (BASE_URI)
+        req.assert_called_with("DELETE", uri)
diff --git a/tests/ip_messaging/test_roles.py b/tests/ip_messaging/test_roles.py
new file mode 100644
index 0000000000..b8485361ee
--- /dev/null
+++ b/tests/ip_messaging/test_roles.py
@@ -0,0 +1,57 @@
+import unittest
+from mock import patch, Mock
+from twilio.rest.resources.ip_messaging import Roles, Role
+from tests.tools import create_mock_json
+
+BASE_URI = "https://ip-messaging.twilio.com/v1/Services/ISxxx"
+ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+AUTH = (ACCOUNT_SID, "token")
+ROLE_SID = "ROaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+
+list_resource = Roles(BASE_URI, AUTH)
+
+
+class RoleTest(unittest.TestCase):
+
+    @patch("twilio.rest.resources.base.make_twilio_request")
+    def test_get_role(self, mock):
+        resp = create_mock_json("tests/resources/ip_messaging/role_instance.json")
+        mock.return_value = resp
+
+        uri = "%s/Roles/%s" % (BASE_URI, ROLE_SID)
+        list_resource.get(ROLE_SID)
+
+        mock.assert_called_with("GET", uri, auth=AUTH,
+                                use_json_extension=False)
+
+    @patch("twilio.rest.resources.base.make_twilio_request")
+    def test_create_role(self, mock):
+        resp = create_mock_json("tests/resources/ip_messaging/role_instance.json")
+        resp.status_code = 201
+        mock.return_value = resp
+
+        list_resource.create("Test Role", "deployment", "createChannel")
+        uri = "%s/Roles" % (BASE_URI)
+        mock.assert_called_with(
+            "POST",
+            uri,
+            data={
+                'FriendlyName': 'Test Role',
+                'Type': 'deployment',
+                'Permission': 'createChannel'
+            },
+            auth=AUTH,
+            use_json_extension=False
+        )
+
+    @patch("twilio.rest.resources.base.Resource.request")
+    def test_delete_role(self, req):
+        resp = Mock()
+        resp.content = ""
+        resp.status_code = 204
+        req.return_value = resp, {}
+
+        app = Role(list_resource, "RO123")
+        app.delete()
+        uri = "%s/Roles/RO123" % (BASE_URI)
+        req.assert_called_with("DELETE", uri)
diff --git a/tests/ip_messaging/test_services.py b/tests/ip_messaging/test_services.py
new file mode 100644
index 0000000000..e8bd5cde85
--- /dev/null
+++ b/tests/ip_messaging/test_services.py
@@ -0,0 +1,53 @@
+import unittest
+from mock import patch, Mock
+from twilio.rest.resources.ip_messaging import Services, Service
+from tests.tools import create_mock_json
+
+BASE_URI = "https://ip-messaging.twilio.com/v1"
+ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+AUTH = (ACCOUNT_SID, "token")
+SERVICE_SID = "ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+
+list_resource = Services(BASE_URI, AUTH)
+
+
+class ServiceTest(unittest.TestCase):
+
+    @patch("twilio.rest.resources.base.make_twilio_request")
+    def test_create_service(self, mock):
+        resp = create_mock_json("tests/resources/ip_messaging/service_instance.json")
+        resp.status_code = 201
+        mock.return_value = resp
+
+        uri = "%s/Services" % (BASE_URI)
+        list_resource.create('TestService')
+        exp_params = {
+            'FriendlyName': "TestService"
+        }
+
+        mock.assert_called_with("POST", uri, data=exp_params, auth=AUTH,
+                                use_json_extension=False)
+
+    @patch("twilio.rest.resources.base.make_twilio_request")
+    def test_get(self, mock):
+        resp = create_mock_json("tests/resources/ip_messaging/service_instance.json")
+        mock.return_value = resp
+
+        uri = "%s/Services/%s" % (BASE_URI, SERVICE_SID)
+        list_resource.get(SERVICE_SID)
+
+        mock.assert_called_with("GET", uri, auth=AUTH,
+                                use_json_extension=False)
+
+    @patch("twilio.rest.resources.base.Resource.request")
+    def test_delete(self, req):
+        """ Deleting a call should work """
+        resp = Mock()
+        resp.content = ""
+        resp.status_code = 204
+        req.return_value = resp, {}
+
+        app = Service(list_resource, "IS123")
+        app.delete()
+        uri = "https://ip-messaging.twilio.com/v1/Services/IS123"
+        req.assert_called_with("DELETE", uri)
diff --git a/tests/ip_messaging/test_users.py b/tests/ip_messaging/test_users.py
new file mode 100644
index 0000000000..7a92c15569
--- /dev/null
+++ b/tests/ip_messaging/test_users.py
@@ -0,0 +1,53 @@
+import unittest
+from mock import patch, Mock
+from twilio.rest.resources.ip_messaging import Users, User
+from tests.tools import create_mock_json
+
+BASE_URI = "https://ip-messaging.twilio.com/v1/Services/ISxxx"
+ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+AUTH = (ACCOUNT_SID, "token")
+USER_SID = "USaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+
+list_resource = Users(BASE_URI, AUTH)
+
+
+class UserTest(unittest.TestCase):
+
+    @patch("twilio.rest.resources.base.make_twilio_request")
+    def test_create_user(self, mock):
+        resp = create_mock_json("tests/resources/ip_messaging/user_instance.json")
+        resp.status_code = 201
+        mock.return_value = resp
+
+        uri = "%s/Users" % (BASE_URI)
+        list_resource.create('test_id')
+        exp_params = {
+            'Identity': "test_id"
+        }
+
+        mock.assert_called_with("POST", uri, data=exp_params, auth=AUTH,
+                                use_json_extension=False)
+
+    @patch("twilio.rest.resources.base.make_twilio_request")
+    def test_get(self, mock):
+        resp = create_mock_json("tests/resources/ip_messaging/user_instance.json")
+        mock.return_value = resp
+
+        uri = "%s/Users/%s" % (BASE_URI, USER_SID)
+        list_resource.get(USER_SID)
+
+        mock.assert_called_with("GET", uri, auth=AUTH,
+                                use_json_extension=False)
+
+    @patch("twilio.rest.resources.base.Resource.request")
+    def test_delete(self, req):
+        """ Deleting a call should work """
+        resp = Mock()
+        resp.content = ""
+        resp.status_code = 204
+        req.return_value = resp, {}
+
+        app = User(list_resource, "US123")
+        app.delete()
+        uri = "%s/Users/US123" % (BASE_URI)
+        req.assert_called_with("DELETE", uri)
diff --git a/tests/pricing/test_messaging_countries.py b/tests/pricing/test_messaging_countries.py
new file mode 100644
index 0000000000..8ea1431ee1
--- /dev/null
+++ b/tests/pricing/test_messaging_countries.py
@@ -0,0 +1,88 @@
+import unittest
+from mock import patch
+from nose.tools import assert_equal
+from tests.tools import create_mock_json
+from twilio.rest.resources.pricing.messaging_countries import (
+    MessagingCountries
+)
+
+AUTH = ("AC123", "token")
+BASE_URI = "https://pricing.twilio.com/v1"
+
+
+class MessagingCountriesTest(unittest.TestCase):
+
+    @patch('twilio.rest.resources.base.make_twilio_request')
+    def test_messaging_countries(self, request):
+        resp = create_mock_json(
+            'tests/resources/pricing/messaging_countries_list.json')
+        resp.status_code = 200
+        request.return_value = resp
+
+        countries = MessagingCountries(BASE_URI + "/Messaging", AUTH)
+        result = countries.list()
+
+        assert_equal(result[0].iso_country, "AT")
+        assert_equal(len(result), 2)
+
+        request.assert_called_with(
+            "GET",
+            "{0}/Messaging/Countries".format(BASE_URI),
+            auth=AUTH,
+            use_json_extension=False,
+            params={}
+        )
+
+    @patch('twilio.rest.resources.base.make_twilio_request')
+    def test_messaging_country(self, request):
+        resp = create_mock_json(
+            'tests/resources/pricing/messaging_countries_instance.json')
+        resp.status_code = 200
+        request.return_value = resp
+
+        countries = MessagingCountries(BASE_URI + "/Messaging", AUTH)
+        result = countries.get('US')
+
+        assert_equal(result.iso_country, "US")
+        assert_equal(result.price_unit, "usd")
+        assert_equal(result.outbound_sms_prices[0]['mcc'], "311")
+        assert_equal(result.outbound_sms_prices[0]['mnc'], "484")
+        assert_equal(result.outbound_sms_prices[0]['carrier'], "Verizon")
+        prices = result.outbound_sms_prices[0]['prices']
+
+        assert_equal(prices[0]['number_type'], "mobile")
+        assert_equal(prices[0]['base_price'], "0.0075")
+        assert_equal(prices[0]['current_price'], "0.0070")
+
+        assert_equal(prices[1]['number_type'], "local")
+        assert_equal(prices[1]['base_price'], "0.0075")
+        assert_equal(prices[1]['current_price'], "0.0070")
+
+        assert_equal(prices[2]['number_type'], "shortcode")
+        assert_equal(prices[2]['base_price'], "0.01")
+        assert_equal(prices[2]['current_price'], "0.01")
+
+        assert_equal(prices[3]['number_type'], "toll-free")
+        assert_equal(prices[3]['base_price'], "0.0075")
+        assert_equal(prices[3]['current_price'], "0.0075")
+
+        inbound_sms_prices = result.inbound_sms_prices
+
+        assert_equal(inbound_sms_prices[0]['number_type'], "local")
+        assert_equal(inbound_sms_prices[0]['base_price'], "0.0075")
+        assert_equal(inbound_sms_prices[0]['current_price'], "0.0075")
+
+        assert_equal(inbound_sms_prices[1]['number_type'], "shortcode")
+        assert_equal(inbound_sms_prices[1]['base_price'], "0.0075")
+        assert_equal(inbound_sms_prices[1]['current_price'], "0.005")
+
+        assert_equal(inbound_sms_prices[2]['number_type'], "toll-free")
+        assert_equal(inbound_sms_prices[2]['base_price'], "0.0075")
+        assert_equal(inbound_sms_prices[2]['current_price'], "0.0075")
+
+        request.assert_called_with(
+            "GET",
+            "{0}/Messaging/Countries/US".format(BASE_URI),
+            auth=AUTH,
+            use_json_extension=False,
+        )
diff --git a/tests/requirements.txt b/tests/requirements.txt
index 9262910394..f1df3797ed 100644
--- a/tests/requirements.txt
+++ b/tests/requirements.txt
@@ -3,6 +3,6 @@ mock==0.8.0
 nose
 coverage
 nosexcover
-flake8
+flake8==3.0.3
 mccabe
 wheel>=0.22.0
diff --git a/tests/resources/ip_messaging/channel_instance.json b/tests/resources/ip_messaging/channel_instance.json
new file mode 100644
index 0000000000..938a62013a
--- /dev/null
+++ b/tests/resources/ip_messaging/channel_instance.json
@@ -0,0 +1,16 @@
+{
+  "sid": "CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+  "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+  "service_sid": "ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+  "friendly_name": "update",
+  "unique_name": "unique",
+  "attributes": "",
+  "date_created": "2015-08-20T09:30:24Z",
+  "date_updated": "2015-08-20T09:30:24Z",
+  "created_by": "system",
+  "url": "https://ip-messaging.stage.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Channels/CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+  "links": {
+    "members": "https://ip-messaging.stage.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Channels/CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Member",
+    "messages": "https://ip-messaging.stage.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Channels/CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Messages"
+  }
+}
diff --git a/tests/resources/ip_messaging/credential_instance.json b/tests/resources/ip_messaging/credential_instance.json
new file mode 100644
index 0000000000..9b24940277
--- /dev/null
+++ b/tests/resources/ip_messaging/credential_instance.json
@@ -0,0 +1,8 @@
+{
+  "account_sid":"ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+  "sid":"CRaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+  "friendly_name":"MyApp APN Certificate",
+  "type":"apn",
+  "date_created":"2015-06-30T21:16:50Z",
+  "date_updated":"2015-07-30T21:16:50Z"
+}
diff --git a/tests/resources/ip_messaging/member_instance.json b/tests/resources/ip_messaging/member_instance.json
new file mode 100644
index 0000000000..adc75ddcfe
--- /dev/null
+++ b/tests/resources/ip_messaging/member_instance.json
@@ -0,0 +1,9 @@
+{
+  "sid": "MBaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+  "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+  "channel_sid": "CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+  "space_sid": "SPaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+  "id": "carl@twilio.com",
+  "role": "admin",
+  "url": "/v1/Spaces/SPxx/Channels/CHxx/Members/MBaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+}
diff --git a/tests/resources/ip_messaging/message_instance.json b/tests/resources/ip_messaging/message_instance.json
new file mode 100644
index 0000000000..acbe53124f
--- /dev/null
+++ b/tests/resources/ip_messaging/message_instance.json
@@ -0,0 +1,12 @@
+{
+  "sid": "IMaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+  "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+  "space_sid": "SPaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+  "date_created": "2015-07-23T20:20:10Z",
+  "date_updated": "2015-07-23T20:20:10Z",
+  "was_edited": true,
+  "from": "carl@twilio.com",
+  "to": "CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+  "body": "Hello",
+  "url": "/v1/Spaces/SPxx/Channels/CHxx/Messages/IMaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+}
diff --git a/tests/resources/ip_messaging/role_instance.json b/tests/resources/ip_messaging/role_instance.json
new file mode 100644
index 0000000000..bbd604428c
--- /dev/null
+++ b/tests/resources/ip_messaging/role_instance.json
@@ -0,0 +1,11 @@
+{
+  "sid":"RLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+  "account_sid":"ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+  "space_sid": "SPaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+  "name":"Deployment Admin",
+  "type":"deployment",
+  "permissions":[
+    "createChannel", 
+    "destroyChannel"
+  ]
+}
diff --git a/tests/resources/ip_messaging/service_instance.json b/tests/resources/ip_messaging/service_instance.json
new file mode 100644
index 0000000000..bc4d56e4a9
--- /dev/null
+++ b/tests/resources/ip_messaging/service_instance.json
@@ -0,0 +1,17 @@
+{
+  "sid": "ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+  "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+  "friendly_name": "TestService",
+  "date_created": "2015-10-21T04:15:36Z",
+  "date_updated": "2015-10-21T04:15:36Z",
+  "default_service_role_sid": "RLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+  "default_channel_role_sid": "RLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+  "typing_indicator_timeout": 5,
+  "webhooks": {},
+  "url": "https://ip-messaging.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+  "links": {
+    "channels": "https://ip-messaging.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Channels",
+    "roles": "https://ip-messaging.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Roles",
+    "users": "https://ip-messaging.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Users"
+  }
+}
diff --git a/tests/resources/ip_messaging/user_instance.json b/tests/resources/ip_messaging/user_instance.json
new file mode 100644
index 0000000000..a2326cc20c
--- /dev/null
+++ b/tests/resources/ip_messaging/user_instance.json
@@ -0,0 +1,10 @@
+{
+  "sid": "USaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+  "date_created": "2015-08-19T18:18:00Z",
+  "date_updated": "2015-08-19T18:18:00Z",
+  "identity": "carl@twilio.com",
+  "service_sid": "ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+  "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+  "role_sid": null,
+  "url": "https://ip-messaging.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Users/USaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+}
diff --git a/tests/resources/keys_instance.json b/tests/resources/keys_instance.json
new file mode 100644
index 0000000000..86459b71ec
--- /dev/null
+++ b/tests/resources/keys_instance.json
@@ -0,0 +1,6 @@
+{
+    "sid":"SKaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+    "friendly_name":"Fuzzy Lumpkins' SigningKey",
+    "date_created":"Fri, 13 Mar 2015 13:24:01 +0000",
+    "date_updated":"Fri, 13 Mar 2015 13:24:01 +0000"
+}
diff --git a/tests/resources/keys_list.json b/tests/resources/keys_list.json
new file mode 100644
index 0000000000..e82306cca8
--- /dev/null
+++ b/tests/resources/keys_list.json
@@ -0,0 +1,18 @@
+{
+   "keys":[
+      {
+         "sid":"SK932e398ac43ca670b1609b05ee301e8c",
+         "friendly_name":"Fuzzy Lumpkins' SigningKey",
+         "date_created":"Fri, 13 Mar 2015 13:24:01 +0000",
+         "date_updated":"Fri, 13 Mar 2015 13:24:01 +0000"
+      }
+   ],
+   "first_page_uri":"/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Keys.json?PageSize=50&Page=0",
+   "uri":"/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Keys.json?PageSize=50&Page=0",
+   "next_page_uri":null,
+   "previous_page_uri":null,
+   "page":0,
+   "page_size":50,
+   "start":0,
+   "end":1
+}
diff --git a/tests/resources/pricing/messaging_countries_instance.json b/tests/resources/pricing/messaging_countries_instance.json
new file mode 100644
index 0000000000..2df45d006f
--- /dev/null
+++ b/tests/resources/pricing/messaging_countries_instance.json
@@ -0,0 +1,51 @@
+{
+    "country": "United States",
+    "iso_country": "US",
+    "price_unit": "usd",
+    "outbound_sms_prices": [
+        {
+            "mcc": "311",
+            "mnc": "484",
+            "carrier": "Verizon",
+            "prices": [
+                {
+                    "number_type": "mobile",
+                    "base_price": "0.0075",
+                    "current_price": "0.0070"
+                },
+                {
+                    "number_type": "local",
+                    "base_price": "0.0075",
+                    "current_price": "0.0070"
+                },
+                {
+                    "number_type": "shortcode",
+                    "base_price": "0.01",
+                    "current_price": "0.01"
+                },
+                {
+                    "number_type": "toll-free",
+                    "base_price": "0.0075",
+                    "current_price": "0.0075"
+                }
+            ]
+        }
+    ],
+    "inbound_sms_prices": [
+        {
+            "number_type": "local",
+            "base_price": "0.0075",
+            "current_price": "0.0075"
+        },
+        {
+            "number_type": "shortcode",
+            "base_price": "0.0075",
+            "current_price": "0.005"
+        },
+        {
+            "number_type": "toll-free",
+            "base_price": "0.0075",
+            "current_price": "0.0075"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/tests/resources/pricing/messaging_countries_list.json b/tests/resources/pricing/messaging_countries_list.json
new file mode 100644
index 0000000000..07623348ec
--- /dev/null
+++ b/tests/resources/pricing/messaging_countries_list.json
@@ -0,0 +1,23 @@
+{
+    "meta": {
+        "first_page_url": "https://pricing.twilio.com/v1/Messaging/Countries?PageSize=50&Page=0",
+        "key": "countries",
+        "next_page_url": "https://pricing.twilio.com/v1/Messaging/Countries?PageSize=50&Page=1&PageToken=DNCZ",
+        "page": 0,
+        "page_size": 50,
+        "previous_page_url": null,
+        "url": "https://pricing.twilio.com/v1/Messaging/Countries?PageSize=50&Page=0"
+    },
+    "countries": [
+        {
+            "country": "Austria",
+            "iso_country": "AT",
+            "url": "https://pricing.twilio.com/v1/Messaging/Countries/AT"
+        },
+        {
+            "country": "Australia",
+            "iso_country": "AU",
+            "url": "https://pricing.twilio.com/v1/Messaging/Countries/AU"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/tests/resources/trunking/credential_lists_instance.json b/tests/resources/trunking/credential_lists_instance.json
new file mode 100644
index 0000000000..3f36eb2c30
--- /dev/null
+++ b/tests/resources/trunking/credential_lists_instance.json
@@ -0,0 +1,9 @@
+{
+    "sid": "CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+    "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+    "trunk_sid" : "TK11111111111111111111111111111111",
+    "friendly_name": "Test",
+    "date_created": "2015-01-02T11:23:45Z",
+    "date_updated": "2015-01-02T11:23:45Z",
+    "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/CredentialLists/CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+}
\ No newline at end of file
diff --git a/tests/resources/trunking/credential_lists_list.json b/tests/resources/trunking/credential_lists_list.json
new file mode 100644
index 0000000000..c284afd308
--- /dev/null
+++ b/tests/resources/trunking/credential_lists_list.json
@@ -0,0 +1,22 @@
+{
+    "meta": {
+        "page": 0,
+        "page_size": 50,
+        "first_page_url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/CredentialLists?PageSize=50&Page=0",
+        "previous_page_url": null,
+        "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/CredentialLists?PageSize=50&Page=0",
+        "next_page_url": null,
+        "key": "credential_lists"
+    },
+    "credential_lists": [
+        {
+            "sid": "CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+            "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+            "trunk_sid": "TK11111111111111111111111111111111",
+            "friendly_name": "Test list",
+            "date_created": "2015-05-14T21:00:12Z",
+            "date_updated": "2015-05-14T21:00:12Z",
+            "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/CredentialLists/CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/tests/resources/trunking/ip_access_control_lists_instance.json b/tests/resources/trunking/ip_access_control_lists_instance.json
new file mode 100644
index 0000000000..9959d4bdec
--- /dev/null
+++ b/tests/resources/trunking/ip_access_control_lists_instance.json
@@ -0,0 +1,9 @@
+{
+    "sid": "ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+    "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+    "trunk_sid": "TK11111111111111111111111111111111",
+    "friendly_name": "Test",
+    "date_created": "2014-10-30T23:59:12Z",
+    "date_updated": "2014-10-30T23:59:12Z",
+    "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/IpAccessControlLists/ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+}
\ No newline at end of file
diff --git a/tests/resources/trunking/ip_access_control_lists_list.json b/tests/resources/trunking/ip_access_control_lists_list.json
new file mode 100644
index 0000000000..07d91fdd13
--- /dev/null
+++ b/tests/resources/trunking/ip_access_control_lists_list.json
@@ -0,0 +1,22 @@
+{
+    "ip_access_control_lists": [
+        {
+            "sid": "ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+            "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+            "trunk_sid": "TK11111111111111111111111111111111",
+            "friendly_name": "Test",
+            "date_created": "2014-10-30T23:59:12Z",
+            "date_updated": "2014-10-30T23:59:12Z",
+            "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/IpAccessControlLists/ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+        }
+    ],
+    "meta": {
+        "page": 0,
+        "page_size": 50,
+        "first_page_url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/IpAccessControlLists?PageSize=50&Page=0",
+        "previous_page_url": null,
+        "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/IpAccessControlLists?PageSize=50&Page=0",
+        "next_page_url": null,
+        "key": "ip_access_control_lists"
+    }
+}
\ No newline at end of file
diff --git a/tests/resources/trunking/origination_urls_instance.json b/tests/resources/trunking/origination_urls_instance.json
new file mode 100644
index 0000000000..27087bcc98
--- /dev/null
+++ b/tests/resources/trunking/origination_urls_instance.json
@@ -0,0 +1,13 @@
+{
+    "sid": "OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+    "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+    "trunk_sid": "TK11111111111111111111111111111111",
+    "weight": 10,
+    "enabled": true,
+    "sip_url": "sip:169.10.1.35",
+    "friendly_name": "Name",
+    "priority": 20,
+    "date_created": "2015-09-02T23:15:56Z",
+    "date_updated": "2015-09-02T23:15:56Z",
+    "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/OriginationUrls/OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+}
diff --git a/tests/resources/trunking/origination_urls_list.json b/tests/resources/trunking/origination_urls_list.json
new file mode 100644
index 0000000000..43a41e7419
--- /dev/null
+++ b/tests/resources/trunking/origination_urls_list.json
@@ -0,0 +1,26 @@
+{
+    "meta": {
+        "page": 0,
+        "page_size": 50,
+        "first_page_url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/OriginationUrls?PageSize=50&Page=0",
+        "previous_page_url": null,
+        "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/OriginationUrls?PageSize=50&Page=0",
+        "next_page_url": null,
+        "key": "origination_urls"
+    },
+    "origination_urls": [
+        {
+            "sid": "OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+            "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+            "trunk_sid": "TK11111111111111111111111111111111",
+            "weight": 10,
+            "enabled": true,
+            "sip_url": "sip:169.10.1.35",
+            "friendly_name": "Name",
+            "priority": 20,
+            "date_created": "2015-09-02T23:15:56Z",
+            "date_updated": "2015-09-02T23:15:56Z",
+            "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/OriginationUrls/OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/tests/resources/trunking/phone_numbers_instance.json b/tests/resources/trunking/phone_numbers_instance.json
new file mode 100644
index 0000000000..3a8e59e6b0
--- /dev/null
+++ b/tests/resources/trunking/phone_numbers_instance.json
@@ -0,0 +1,34 @@
+{
+    "sid": "PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+    "date_created": "2014-10-07T13:37:19Z",
+    "date_updated": "2015-09-02T16:27:22Z",
+    "friendly_name": "Name",
+    "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+    "phone_number": "+14158675309",
+    "api_version": "2010-04-01",
+    "voice_caller_id_lookup": false,
+    "voice_url": null,
+    "voice_method": null,
+    "voice_fallback_url": "",
+    "voice_fallback_method": "POST",
+    "status_callback": "",
+    "status_callback_method": "POST",
+    "voice_application_sid": null,
+    "trunk_sid": "TK11111111111111111111111111111111",
+    "sms_url": "https://demo.twilio.com/welcome/sms/reply/",
+    "sms_method": "POST",
+    "sms_fallback_url": "",
+    "sms_fallback_method": "POST",
+    "sms_application_sid": "",
+    "address_requirements": "none",
+    "beta": false,
+    "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/PhoneNumbers/PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+    "capabilities": {
+        "voice": true,
+        "sms": true,
+        "mms": true
+    },
+    "links": {
+        "phone_number": "https://api.twilio.com/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/IncomingPhoneNumbers/PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+    }
+}
\ No newline at end of file
diff --git a/tests/resources/trunking/phone_numbers_list.json b/tests/resources/trunking/phone_numbers_list.json
new file mode 100644
index 0000000000..d24d62195c
--- /dev/null
+++ b/tests/resources/trunking/phone_numbers_list.json
@@ -0,0 +1,47 @@
+{
+    "phone_numbers": [
+        {
+            "sid": "PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+            "date_created": "2014-10-07T13:37:19Z",
+            "date_updated": "2015-09-02T16:27:22Z",
+            "friendly_name": "Name",
+            "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+            "phone_number": "+14158675309",
+            "api_version": "2010-04-01",
+            "voice_caller_id_lookup": false,
+            "voice_url": null,
+            "voice_method": null,
+            "voice_fallback_url": "",
+            "voice_fallback_method": "POST",
+            "status_callback": "",
+            "status_callback_method": "POST",
+            "voice_application_sid": null,
+            "trunk_sid": "TK11111111111111111111111111111111",
+            "sms_url": "https://demo.twilio.com/welcome/sms/reply/",
+            "sms_method": "POST",
+            "sms_fallback_url": "",
+            "sms_fallback_method": "POST",
+            "sms_application_sid": "",
+            "address_requirements": "none",
+            "beta": false,
+            "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/PhoneNumbers/PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+            "capabilities": {
+                "voice": true,
+                "sms": true,
+                "mms": true
+            },
+            "links": {
+                "phone_number": "https://api.twilio.com/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/IncomingPhoneNumbers/PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+            }
+        }
+    ],
+    "meta": {
+        "page": 0,
+        "page_size": 50,
+        "first_page_url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/PhoneNumbers?PageSize=50&Page=0",
+        "previous_page_url": null,
+        "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/PhoneNumbers?PageSize=50&Page=0",
+        "next_page_url": null,
+        "key": "phone_numbers"
+    }
+}
\ No newline at end of file
diff --git a/tests/resources/trunking/trunks_instance.json b/tests/resources/trunking/trunks_instance.json
new file mode 100644
index 0000000000..1d23a71baa
--- /dev/null
+++ b/tests/resources/trunking/trunks_instance.json
@@ -0,0 +1,26 @@
+{
+    "sid": "TK11111111111111111111111111111111",
+    "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+    "domain_name": "test-trunk.pstn.twilio.com",
+    "disaster_recovery_method": null,
+    "disaster_recovery_url": null,
+    "friendly_name": "Test",
+    "secure": false,
+    "recording": {
+        "trim": "do-not-trim",
+        "mode": "record-from-ringing"
+    },
+    "auth_type": "CREDENTIAL_LIST",
+    "auth_type_set": [
+       "CREDENTIAL_LIST"
+    ],
+    "date_created": "2015-05-05T20:59:07Z",
+    "date_updated": "2015-05-05T22:44:23Z",
+    "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111",
+    "links": {
+        "origination_urls": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/OriginationUrls",
+        "credential_lists": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/CredentialLists",
+        "ip_access_control_lists": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/IpAccessControlLists",
+        "phone_numbers": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/PhoneNumbers"
+    }
+}
\ No newline at end of file
diff --git a/tests/resources/trunking/trunks_list.json b/tests/resources/trunking/trunks_list.json
new file mode 100644
index 0000000000..b2c238e143
--- /dev/null
+++ b/tests/resources/trunking/trunks_list.json
@@ -0,0 +1,39 @@
+{
+    "trunks": [
+        {
+            "sid": "TK11111111111111111111111111111111",
+            "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+            "domain_name": "test-trunk.pstn.twilio.com",
+            "disaster_recovery_method": null,
+            "disaster_recovery_url": null,
+            "friendly_name": "Test",
+            "secure": false,
+            "recording": {
+                "trim": "do-not-trim",
+                "mode": "record-from-ringing"
+            },
+            "auth_type": "CREDENTIAL_LIST",
+            "auth_type_set": [
+               "CREDENTIAL_LIST"
+            ],
+            "date_created": "2015-05-05T20:59:07Z",
+            "date_updated": "2015-05-05T22:44:23Z",
+            "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111",
+            "links": {
+                "origination_urls": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/OriginationUrls",
+                "credential_lists": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/CredentialLists",
+                "ip_access_control_lists": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/IpAccessControlLists",
+                "phone_numbers": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/PhoneNumbers"
+            }
+        }
+    ],
+    "meta": {
+        "page": 0,
+        "page_size": 50,
+        "first_page_url": "https://trunking.twilio.com/v1/Trunks?PageSize=50&Page=0",
+        "previous_page_url": null,
+        "url": "https://trunking.twilio.com/v1/Trunks?PageSize=50&Page=0",
+        "next_page_url": null,
+        "key": "trunks"
+    }
+}
\ No newline at end of file
diff --git a/tests/task_router/test_capability.py b/tests/task_router/test_capability.py
index abfb25b0f5..70a5e46bb1 100644
--- a/tests/task_router/test_capability.py
+++ b/tests/task_router/test_capability.py
@@ -55,8 +55,7 @@ def test_defaults(self):
 
         self.assertTrue(decoded is not None)
         websocket_url = (
-            'https://event-bridge.twilio.com/v1/wschannels/%s/%s' %
-            (self.account_sid, self.worker_sid)
+            'https://event-bridge.twilio.com/v1/wschannels/{0}/{1}'.format(self.account_sid, self.worker_sid)
         )
         expected = [
             {
@@ -74,13 +73,33 @@ def test_defaults(self):
                 'post_filter': {},
             },
             {
-                'url':
-                'https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities',
+                'url': 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities',
                 'method': 'GET',
                 'allow': True,
                 'query_filter': {},
                 'post_filter': {},
             },
+            {
+                'url': 'https://taskrouter.twilio.com/v1/Workspaces/{0}/Tasks/**'.format(self.workspace_sid),
+                'method': 'GET',
+                'allow': True,
+                'query_filter': {},
+                'post_filter': {},
+            },
+            {
+                'url': 'https://taskrouter.twilio.com/v1/Workspaces/{0}/Workers/{1}/Reservations/**'.format(self.workspace_sid, self.worker_sid),
+                'method': 'GET',
+                'allow': True,
+                'query_filter': {},
+                'post_filter': {},
+            },
+            {
+                'url': 'https://taskrouter.twilio.com/v1/Workspaces/{0}/Workers/{1}'.format(self.workspace_sid, self.worker_sid),
+                'method': 'GET',
+                'allow': True,
+                'query_filter': {},
+                'post_filter': {},
+            }
         ]
         self.assertEqual(expected, decoded['policies'])
 
@@ -90,7 +109,7 @@ def test_allow_worker_activity_updates(self):
         decoded = jwt.decode(token, self.auth_token)
 
         self.assertTrue(decoded is not None)
-        url = 'https://taskrouter.twilio.com/v1/Workspaces/%s/Workers/%s' % (
+        url = 'https://taskrouter.twilio.com/v1/Workspaces/{0}/Workers/{1}'.format(
             self.workspace_sid,
             self.worker_sid,
         )
@@ -110,7 +129,7 @@ def test_allow_worker_fetch_attributes(self):
         decoded = jwt.decode(token, self.auth_token)
 
         self.assertTrue(decoded is not None)
-        url = 'https://taskrouter.twilio.com/v1/Workspaces/%s/Workers/%s' % (
+        url = 'https://taskrouter.twilio.com/v1/Workspaces/{0}/Workers/{1}'.format(
             self.workspace_sid,
             self.worker_sid,
         )
@@ -131,7 +150,7 @@ def test_allow_task_reservation_updates(self):
         decoded = jwt.decode(token, self.auth_token)
 
         self.assertTrue(decoded is not None)
-        url = 'https://taskrouter.twilio.com/v1/Workspaces/%s/Tasks/**' % (
+        url = 'https://taskrouter.twilio.com/v1/Workspaces/{0}/Tasks/**'.format(
             self.workspace_sid,
         )
 
@@ -140,6 +159,6 @@ def test_allow_task_reservation_updates(self):
             'method': 'POST',
             'allow': True,
             'query_filter': {},
-            'post_filter': {'ReservationStatus': {'required': True}},
+            'post_filter': {},
         }
         self.assertEqual(expected, decoded['policies'][-1])
diff --git a/tests/task_router/test_reservations.py b/tests/task_router/test_reservations.py
index ff85ffa316..5d635dad9a 100644
--- a/tests/task_router/test_reservations.py
+++ b/tests/task_router/test_reservations.py
@@ -8,6 +8,7 @@
 
 AUTH = ("AC123", "token")
 BASE_URI = "https://taskrouter.twilio.com/v1/Accounts/AC123/Workspaces/WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Tasks/WTaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+BASE_WORKER_URI = "https://taskrouter.twilio.com/v1/Accounts/AC123/Workspaces/WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Workers/WKaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
 RESERVATION_SID = "WRaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
 
 
@@ -36,6 +37,18 @@ def test_list(self, request):
         request.assert_called_with("GET", uri, params={}, auth=AUTH,
                                    use_json_extension=False)
 
+    @patch('twilio.rest.resources.base.make_twilio_request')
+    def test_list_for_worker(self, request):
+        resp = create_mock_json('tests/resources/task_router/reservations_list.json')
+        resp.status_code = 200
+        request.return_value = resp
+
+        uri = "{0}/Reservations".format(BASE_WORKER_URI)
+        list_resource = Reservations(BASE_WORKER_URI, AUTH)
+        list_resource.list()
+        request.assert_called_with("GET", uri, params={}, auth=AUTH,
+                                   use_json_extension=False)
+
     @patch('twilio.rest.resources.base.make_twilio_request')
     def test_update_instance(self, request):
         resp = create_mock_json('tests/resources/task_router/reservations_instance.json')
diff --git a/tests/task_router/test_task_router_capability.py b/tests/task_router/test_task_router_capability.py
new file mode 100644
index 0000000000..e1d67efad9
--- /dev/null
+++ b/tests/task_router/test_task_router_capability.py
@@ -0,0 +1,168 @@
+import unittest
+import warnings
+
+from twilio import jwt
+from twilio.task_router import TaskRouterCapability
+
+
+class TaskRouterCapabilityTest(unittest.TestCase):
+
+    def check_policy(self, method, url, policy):
+        self.assertEqual(url, policy['url'])
+        self.assertEqual(method, policy['method'])
+        self.assertTrue(policy['allow'])
+        self.assertEqual({}, policy['query_filter'])
+        self.assertEqual({}, policy['post_filter'])
+
+    def check_decoded(self, decoded, account_sid, workspace_sid, channel_id, channel_sid=None):
+        self.assertEqual(decoded["iss"], account_sid)
+        self.assertEqual(decoded["account_sid"], account_sid)
+        self.assertEqual(decoded["workspace_sid"], workspace_sid)
+        self.assertEqual(decoded["channel"], channel_id)
+        self.assertEqual(decoded["version"], "v1")
+        self.assertEqual(decoded["friendly_name"], channel_id)
+
+        if 'worker_sid' in decoded.keys():
+            self.assertEqual(decoded['worker_sid'], channel_sid)
+        if 'taskqueue_sid' in decoded.keys():
+            self.assertEqual(decoded['taskqueue_sid'], channel_sid)
+
+    def test_workspace_default(self):
+        account_sid = "AC123"
+        auth_token = "foobar"
+        workspace_sid = "WS456"
+        channel_id = "WS456"
+        capability = TaskRouterCapability(account_sid, auth_token, workspace_sid, channel_id)
+
+        capability.generate_token()
+
+        token = capability.generate_token()
+        self.assertNotEqual(None, token)
+
+        decoded = jwt.decode(token, auth_token)
+        self.assertNotEqual(None, decoded)
+
+        self.check_decoded(decoded, account_sid, workspace_sid, channel_id)
+
+        policies = decoded['policies']
+        self.assertEqual(len(policies), 3)
+
+        for method, url, policy in [
+            ('GET', "https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", policies[0]),
+            ('POST', "https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", policies[1]),
+            ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456", policies[2]),
+        ]:
+            yield self.check_policy, method, url, policy
+
+    def test_worker_default(self):
+        account_sid = "AC123"
+        auth_token = "foobar"
+        workspace_sid = "WS456"
+        worker_sid = "WK789"
+        capability = TaskRouterCapability(account_sid, auth_token, workspace_sid, worker_sid)
+
+        capability.generate_token()
+
+        token = capability.generate_token()
+        self.assertNotEqual(None, token)
+
+        decoded = jwt.decode(token, auth_token)
+        self.assertNotEqual(None, decoded)
+
+        self.check_decoded(decoded, account_sid, workspace_sid, worker_sid, worker_sid)
+
+        policies = decoded['policies']
+        self.assertEqual(len(policies), 6)
+
+        for method, url, policy in [
+            ('GET', "https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", policies[0]),
+            ('POST', "https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", policies[1]),
+            ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789", policies[2])
+            ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities", policies[3]),
+            ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**", policies[4]),
+            ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789/Reservations/**", policies[5])
+        ]:
+            yield self.check_policy, method, url, policy
+
+    def test_task_queue_default(self):
+        account_sid = "AC123"
+        auth_token = "foobar"
+        workspace_sid = "WS456"
+        taskqueue_sid = "WQ789"
+        capability = TaskRouterCapability(account_sid, auth_token, workspace_sid, taskqueue_sid)
+
+        capability.generate_token()
+
+        token = capability.generate_token()
+        self.assertNotEqual(None, token)
+
+        decoded = jwt.decode(token, auth_token)
+        self.assertNotEqual(None, decoded)
+
+        self.check_decoded(decoded, account_sid, workspace_sid, taskqueue_sid, taskqueue_sid)
+
+        policies = decoded['policies']
+        self.assertEqual(len(policies), 3)
+
+        for method, url, policy in [
+            ('GET', "https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", policies[0]),
+            ('POST', "https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", policies[1])
+            ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789", policies[2])
+        ]:
+            yield self.check_policy, method, url, policy
+
+    def test_deprecated_worker(self):
+        account_sid = "AC123"
+        auth_token = "foobar"
+        workspace_sid = "WS456"
+        worker_sid = "WK789"
+        capability = TaskRouterCapability(account_sid, auth_token, workspace_sid, worker_sid)
+
+        capability.generate_token()
+
+        token = capability.generate_token()
+        self.assertNotEqual(None, token)
+
+        decoded = jwt.decode(token, auth_token)
+        self.assertNotEqual(None, decoded)
+
+        self.check_decoded(decoded, account_sid, workspace_sid, worker_sid, worker_sid)
+
+        policies = decoded['policies']
+        self.assertEqual(len(policies), 6)
+
+        # should expect 6 policies
+        for method, url, policy in [
+            ('GET', "https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", policies[0]),
+            ('POST', "https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", policies[1]),
+            ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities", policies[2]),
+            ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**", policies[3]),
+            ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789/Reservations/**", policies[4]),
+            ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789", policies[5])
+        ]:
+            yield self.check_policy, method, url, policy
+
+        # check deprecated warnings
+        with warnings.catch_warnings(record=True) as w:
+            warnings.simplefilter("always")
+            capability.allow_worker_fetch_attributes()
+            assert len(w) == 1
+            assert issubclass(w[-1].category, DeprecationWarning)
+            assert "deprecated" in str(w[-1].message)
+
+        with warnings.catch_warnings(record=True) as w:
+            warnings.simplefilter("always")
+            capability.allow_worker_activity_updates()
+            assert len(w) == 1
+            assert issubclass(w[-1].category, DeprecationWarning)
+            assert "deprecated" in str(w[-1].message)
+
+        with warnings.catch_warnings(record=True) as w:
+            warnings.simplefilter("always")
+            capability.allow_task_reservation_updates()
+            assert len(w) == 1
+            assert issubclass(w[-1].category, DeprecationWarning)
+            assert "deprecated" in str(w[-1].message)
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/tests/task_router/test_task_router_taskqueue_capability.py b/tests/task_router/test_task_router_taskqueue_capability.py
new file mode 100644
index 0000000000..42ed9597e6
--- /dev/null
+++ b/tests/task_router/test_task_router_taskqueue_capability.py
@@ -0,0 +1,132 @@
+import time
+import unittest
+
+from twilio import jwt
+from twilio.task_router import TaskRouterTaskQueueCapability
+
+
+class TaskRouterTaskQueueCapabilityTest(unittest.TestCase):
+
+    def setUp(self):
+        self.account_sid = "AC123"
+        self.auth_token = "foobar"
+        self.workspace_sid = "WS456"
+        self.taskqueue_sid = "WQ789"
+        self.capability = TaskRouterTaskQueueCapability(self.account_sid, self.auth_token, self.workspace_sid, self.taskqueue_sid)
+
+    def test_generate_token(self):
+
+        token = self.capability.generate_token()
+        self.assertNotEqual(None, token)
+
+        decoded = jwt.decode(token, self.auth_token)
+        self.assertNotEqual(None, decoded)
+
+        self.assertEqual(decoded["iss"], self.account_sid)
+        self.assertEqual(decoded["account_sid"], self.account_sid)
+        self.assertEqual(decoded["workspace_sid"], self.workspace_sid)
+        self.assertEqual(decoded["taskqueue_sid"], self.taskqueue_sid)
+        self.assertEqual(decoded["channel"], self.taskqueue_sid)
+        self.assertEqual(decoded["version"], "v1")
+        self.assertEqual(decoded["friendly_name"], self.taskqueue_sid)
+
+    def test_generate_token_with_default_ttl(self):
+        token = self.capability.generate_token()
+        self.assertNotEqual(None, token)
+
+        decoded = jwt.decode(token, self.auth_token)
+        self.assertNotEqual(None, decoded)
+
+        self.assertEqual(int(time.time()) + 3600, decoded["exp"])
+
+    def test_generate_token_with_custom_ttl(self):
+        ttl = 10000
+
+        token = self.capability.generate_token(ttl)
+        self.assertNotEqual(None, token)
+
+        decoded = jwt.decode(token, self.auth_token)
+        self.assertNotEqual(None, decoded)
+
+        self.assertEqual(int(time.time()) + 10000, decoded["exp"])
+
+    def test_default(self):
+        token = self.capability.generate_token()
+        self.assertNotEqual(None, token)
+
+        decoded = jwt.decode(token, self.auth_token)
+        self.assertNotEqual(None, decoded)
+
+        policies = decoded['policies']
+        self.assertEqual(len(policies), 3)
+
+        # websocket GET
+        get_policy = policies[0]
+        self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", get_policy['url'])
+        self.assertEqual("GET", get_policy['method'])
+        self.assertTrue(get_policy['allow'])
+        self.assertEqual({}, get_policy['query_filter'])
+        self.assertEqual({}, get_policy['post_filter'])
+
+        # websocket POST
+        post_policy = policies[1]
+        self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", post_policy['url'])
+        self.assertEqual("POST", post_policy['method'])
+        self.assertTrue(post_policy['allow'])
+        self.assertEqual({}, post_policy['query_filter'])
+        self.assertEqual({}, post_policy['post_filter'])
+
+        # fetch GET
+        fetch_policy = policies[2]
+        self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789", fetch_policy['url'])
+        self.assertEqual("GET", fetch_policy['method'])
+        self.assertTrue(fetch_policy['allow'])
+        self.assertEqual({}, fetch_policy['query_filter'])
+        self.assertEqual({}, fetch_policy['post_filter'])
+
+    def test_allow_fetch_subresources(self):
+        self.capability.allow_fetch_subresources()
+
+        token = self.capability.generate_token()
+        self.assertNotEqual(None, token)
+
+        decoded = jwt.decode(token, self.auth_token)
+        self.assertNotEqual(None, decoded)
+
+        policies = decoded['policies']
+        self.assertEqual(len(policies), 4)
+
+        # confirm the additional policy generated with allow_fetch_subresources()
+
+        policy = policies[3]
+
+        self.assertEqual(policy['url'], "https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789/**")
+        self.assertEqual(policy['method'], "GET")
+        self.assertTrue(policy['allow'])
+        self.assertEqual({}, policy['query_filter'])
+        self.assertEqual({}, policy['post_filter'])
+
+    def test_allow_updates_subresources(self):
+        self.capability.allow_updates_subresources()
+
+        token = self.capability.generate_token()
+        self.assertNotEqual(None, token)
+
+        decoded = jwt.decode(token, self.auth_token)
+        self.assertNotEqual(None, decoded)
+
+        policies = decoded['policies']
+        self.assertEqual(len(policies), 4)
+
+        # confirm the additional policy generated with allow_updates_subresources()
+
+        policy = policies[3]
+
+        self.assertEqual(policy['url'], "https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789/**")
+        self.assertEqual(policy['method'], "POST")
+        self.assertTrue(policy['allow'])
+        self.assertEqual({}, policy['query_filter'])
+        self.assertEqual({}, policy['post_filter'])
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/tests/task_router/test_task_router_worker_capability.py b/tests/task_router/test_task_router_worker_capability.py
new file mode 100644
index 0000000000..97ac7d0160
--- /dev/null
+++ b/tests/task_router/test_task_router_worker_capability.py
@@ -0,0 +1,136 @@
+import time
+import unittest
+
+from twilio import jwt
+from twilio.task_router import TaskRouterWorkerCapability
+
+
+class TaskRouterWorkerCapabilityTest(unittest.TestCase):
+    def check_policy(self, method, url, policy):
+        self.assertEqual(url, policy['url'])
+        self.assertEqual(method, policy['method'])
+        self.assertTrue(policy['allow'])
+        self.assertEqual({}, policy['query_filter'])
+        self.assertEqual({}, policy['post_filter'])
+
+    def check_decoded(self, decoded, account_sid, workspace_sid, channel_id, channel_sid=None):
+        self.assertEqual(decoded["iss"], account_sid)
+        self.assertEqual(decoded["account_sid"], account_sid)
+        self.assertEqual(decoded["workspace_sid"], workspace_sid)
+        self.assertEqual(decoded["channel"], channel_id)
+        self.assertEqual(decoded["version"], "v1")
+        self.assertEqual(decoded["friendly_name"], channel_id)
+
+        if 'worker_sid' in decoded.keys():
+            self.assertEqual(decoded['worker_sid'], channel_sid)
+        if 'taskqueue_sid' in decoded.keys():
+            self.assertEqual(decoded['taskqueue_sid'], channel_sid)
+
+    def setUp(self):
+        self.account_sid = "AC123"
+        self.auth_token = "foobar"
+        self.workspace_sid = "WS456"
+        self.worker_sid = "WK789"
+        self.capability = TaskRouterWorkerCapability(self.account_sid, self.auth_token, self.workspace_sid, self.worker_sid)
+
+    def test_generate_token(self):
+
+        token = self.capability.generate_token()
+        self.assertNotEqual(None, token)
+
+        decoded = jwt.decode(token, self.auth_token)
+        self.assertNotEqual(None, decoded)
+
+        self.check_decoded(decoded, self.account_sid, self.workspace_sid, self.worker_sid, self.worker_sid)
+
+    def test_generate_token_with_default_ttl(self):
+        token = self.capability.generate_token()
+        self.assertNotEqual(None, token)
+
+        decoded = jwt.decode(token, self.auth_token)
+        self.assertNotEqual(None, decoded)
+
+        self.assertEqual(int(time.time()) + 3600, decoded["exp"])
+
+    def test_generate_token_with_custom_ttl(self):
+        ttl = 10000
+
+        token = self.capability.generate_token(ttl)
+        self.assertNotEqual(None, token)
+
+        decoded = jwt.decode(token, self.auth_token)
+        self.assertNotEqual(None, decoded)
+
+        self.assertEqual(int(time.time()) + 10000, decoded["exp"])
+
+    def test_defaults(self):
+        token = self.capability.generate_token()
+        self.assertNotEqual(None, token)
+
+        decoded = jwt.decode(token, self.auth_token)
+        self.assertNotEqual(None, decoded)
+
+        websocket_url = 'https://event-bridge.twilio.com/v1/wschannels/{0}/{1}'.format(self.account_sid, self.worker_sid)
+
+        # expect 6 policies
+        policies = decoded['policies']
+        self.assertEqual(len(policies), 6)
+
+        # should expect 6 policies
+        for method, url, policy in [
+            ('GET', websocket_url, policies[0]),
+            ('POST', websocket_url, policies[1]),
+            ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789", policies[2]),
+            ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities", policies[3])
+            ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**", policies[4]),
+            ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789/Reservations/**", policies[5])
+        ]:
+            yield self.check_policy, method, url, policy
+
+    def test_allow_activity_updates(self):
+
+        # allow activity updates to the worker
+        self.capability.allow_activity_updates()
+
+        token = self.capability.generate_token()
+        self.assertNotEqual(None, token)
+
+        decoded = jwt.decode(token, self.auth_token)
+        self.assertNotEqual(None, decoded)
+
+        policies = decoded['policies']
+        self.assertEqual(len(policies), 7)
+        policy = policies[6]
+
+        url = "https://taskrouter.twilio.com/v1/Workspaces/{0}/Workers/{1}".format(self.workspace_sid, self.worker_sid)
+
+        self.assertEqual(url, policy["url"])
+        self.assertEqual("POST", policy["method"])
+        self.assertTrue(policy["allow"])
+        self.assertNotEqual(None, policy['post_filter'])
+        self.assertEqual({}, policy['query_filter'])
+        self.assertTrue(policy['post_filter']['ActivitySid'])
+
+    def test_allow_reservation_updates(self):
+        # allow reservation updates
+        self.capability.allow_reservation_updates()
+
+        token = self.capability.generate_token()
+        self.assertNotEqual(None, token)
+
+        decoded = jwt.decode(token, self.auth_token)
+        self.assertNotEqual(None, decoded)
+
+        policies = decoded['policies']
+        self.assertEqual(len(policies), 8)
+
+        taskPolicy = policies[6]
+        tasksUrl = "https://taskrouter.twilio.com/v1/Workspaces/{0}/Tasks/**".format(self.workspace_sid)
+        self.check_policy('POST', tasksUrl, taskPolicy)
+
+        workerReservationsPolicy = policies[7]
+        reservationsUrl = "https://taskrouter.twilio.com/v1/Workspaces/{0}/Workers/{1}/Reservations/**".format(self.workspace_sid, self.worker_sid)
+        self.check_policy('POST', reservationsUrl, workerReservationsPolicy)
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/tests/task_router/test_task_router_workspace_capability.py b/tests/task_router/test_task_router_workspace_capability.py
new file mode 100644
index 0000000000..048611166e
--- /dev/null
+++ b/tests/task_router/test_task_router_workspace_capability.py
@@ -0,0 +1,117 @@
+import time
+import unittest
+
+from twilio import jwt
+from twilio.task_router import TaskRouterWorkspaceCapability
+
+
+class TaskRouterWorkspaceCapabilityTest(unittest.TestCase):
+    def check_policy(self, method, url, policy):
+        self.assertEqual(url, policy['url'])
+        self.assertEqual(method, policy['method'])
+        self.assertTrue(policy['allow'])
+        self.assertEqual({}, policy['query_filter'])
+        self.assertEqual({}, policy['post_filter'])
+
+    def check_decoded(self, decoded, account_sid, workspace_sid, channel_id, channel_sid=None):
+        self.assertEqual(decoded["iss"], account_sid)
+        self.assertEqual(decoded["account_sid"], account_sid)
+        self.assertEqual(decoded["workspace_sid"], workspace_sid)
+        self.assertEqual(decoded["channel"], channel_id)
+        self.assertEqual(decoded["version"], "v1")
+        self.assertEqual(decoded["friendly_name"], channel_id)
+
+        if 'worker_sid' in decoded.keys():
+            self.assertEqual(decoded['worker_sid'], channel_sid)
+        if 'taskqueue_sid' in decoded.keys():
+            self.assertEqual(decoded['taskqueue_sid'], channel_sid)
+
+    def setUp(self):
+        self.account_sid = "AC123"
+        self.auth_token = "foobar"
+        self.workspace_sid = "WS456"
+        self.capability = TaskRouterWorkspaceCapability(self.account_sid, self.auth_token, self.workspace_sid)
+
+    def test_generate_token(self):
+
+        token = self.capability.generate_token()
+        self.assertNotEqual(None, token)
+
+        decoded = jwt.decode(token, self.auth_token)
+        self.assertNotEqual(None, decoded)
+
+        self.check_decoded(decoded, self.account_sid, self.workspace_sid, self.workspace_sid)
+
+    def test_generate_token_with_default_ttl(self):
+        token = self.capability.generate_token()
+        self.assertNotEqual(None, token)
+
+        decoded = jwt.decode(token, self.auth_token)
+        self.assertNotEqual(None, decoded)
+
+        self.assertEqual(int(time.time()) + 3600, decoded["exp"])
+
+    def test_generate_token_with_custom_ttl(self):
+        ttl = 10000
+
+        token = self.capability.generate_token(ttl)
+        self.assertNotEqual(None, token)
+
+        decoded = jwt.decode(token, self.auth_token)
+        self.assertNotEqual(None, decoded)
+
+        self.assertEqual(int(time.time()) + 10000, decoded["exp"])
+
+    def test_default(self):
+        token = self.capability.generate_token()
+        self.assertNotEqual(None, token)
+
+        decoded = jwt.decode(token, self.auth_token)
+        self.assertNotEqual(None, decoded)
+
+        policies = decoded['policies']
+        self.assertEqual(len(policies), 3)
+
+        for method, url, policy in [
+            ('GET', "https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", policies[0]),
+            ('POST', "https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", policies[1]),
+            ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456", policies[2])
+        ]:
+            yield self.check_policy, method, url, policy
+
+    def test_allow_fetch_subresources(self):
+        self.capability.allow_fetch_subresources()
+
+        token = self.capability.generate_token()
+        self.assertNotEqual(None, token)
+
+        decoded = jwt.decode(token, self.auth_token)
+        self.assertNotEqual(None, decoded)
+
+        policies = decoded['policies']
+        self.assertEqual(len(policies), 4)
+
+        # confirm the additional policy generated with allow_fetch_subresources()
+        policy = policies[3]
+
+        self.check_policy('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/**", policy)
+
+    def test_allow_updates_subresources(self):
+        self.capability.allow_updates_subresources()
+
+        token = self.capability.generate_token()
+        self.assertNotEqual(None, token)
+
+        decoded = jwt.decode(token, self.auth_token)
+        self.assertNotEqual(None, decoded)
+
+        policies = decoded['policies']
+        self.assertEqual(len(policies), 4)
+
+        # confirm the additional policy generated with allow_updates_subresources()
+        policy = policies[3]
+
+        self.check_policy('POST', "https://taskrouter.twilio.com/v1/Workspaces/WS456/**", policy)
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/tests/task_router/test_workflow_config.py b/tests/task_router/test_workflow_config.py
new file mode 100644
index 0000000000..4aa99213de
--- /dev/null
+++ b/tests/task_router/test_workflow_config.py
@@ -0,0 +1,284 @@
+import unittest
+import json
+
+from twilio.task_router.workflow_config import WorkflowConfig
+from twilio.task_router.workflow_rule import WorkflowRule
+from twilio.task_router.workflow_ruletarget import WorkflowRuleTarget
+
+
+class WorkflowConfigTest(unittest.TestCase):
+        def test_to_json(self):
+            rules = [
+                WorkflowRule("1==1", [WorkflowRuleTarget("WQeae4fc2f4db7f377c5d3758fb08b79b7", "1==1", 1, 20)], "SomeQ"),
+                WorkflowRule("1==1", [WorkflowRuleTarget("WQ19ebe92fb33522f018b5a31d805d94da", "1==1", 1, 210)], "SomeOtherQ")
+            ]
+            def_target = WorkflowRuleTarget("WQ9963154bf3122d0a0558f3763951d916", "1==1", None, None)
+            config = WorkflowConfig(rules, def_target)
+            self.assertEqual(True, self.is_json(config.to_json()))
+
+        def test_from_json(self):
+
+            data = {
+                       'task_routing':
+                       {
+                           'filters': [
+                               {
+                                   'expression': 'type == "sales"',
+                                   'friendly_name': 'Sales',
+                                   'targets': [
+                                       {
+                                           'queue': 'WQec62de0e1148b8477f2e24579779c8b1',
+                                           'expression': 'task.language IN worker.languages'
+                                       }
+                                   ]
+                               },
+                               {
+                                   'expression': 'type == "marketing"',
+                                   'friendly_name': 'Marketing',
+                                   'targets': [
+                                       {
+                                           'queue': 'WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f',
+                                           'expression': 'task.language IN worker.languages'
+                                       }
+                                   ]
+                               },
+                               {
+                                   'expression': 'type == "support"',
+                                   'friendly_name': 'Support',
+                                   'targets': [
+                                       {
+                                           'queue': 'WQe5eb317eb23500ade45087ea6522896c',
+                                           'expression': 'task.language IN worker.languages'
+                                       }
+                                   ]
+                               }
+                           ],
+                           'default_filter':
+                           {
+                               'queue': 'WQ05f810d2d130344fd56e3c91ece2e594'
+                           }
+                       }
+                   }
+
+            config = WorkflowConfig.json2obj(json.dumps(data))
+            self.assertEqual(3, len(config.task_routing.filters))
+            self.assertEqual(1, len(config.task_routing.default_filter))
+
+        def test_from_json2(self):
+
+            data = {'task_routing': {'filters': [{'friendly_name': 'SomeQ', 'expression': '1==1', 'targets': [
+                {'priority': 1, 'queue': 'WQXXXX', 'expression': '1==1', 'timeout': 20}]},
+                                                  {'friendly_name': 'SomeOtherQ', 'expression': '1==1',
+                                                   'targets': [
+                                                       {'priority': 1, 'queue': 'WQXXXX', 'expression': '1==1',
+                                                        'timeout': 20}]}],
+                                      'default_filter': {'priority': None, 'queue': 'WQYYYYY', 'expression': None,
+                                                         'timeout': None}}}
+            config = WorkflowConfig.json2obj(json.dumps(data))
+            self.assertEqual(2, len(config.task_routing.filters))
+            self.assertEqual(4, len(config.task_routing.default_filter))
+
+        def test_from_json_with_filter_friendly_name(self):
+            data = {
+                       'task_routing':
+                       {
+                           'filters': [
+                               {
+                                   'expression': 'type == "sales"',
+                                   'filter_friendly_name': 'Sales',
+                                   'targets': [
+                                       {
+
+                                           'queue': 'WQec62de0e1148b8477f2e24579779c8b1',
+                                           'expression': 'task.language IN worker.languages'
+                                       }
+                                   ]
+                               },
+                               {
+                                   'expression': 'type == "marketing"',
+                                   'filter_friendly_name': 'Marketing',
+                                   'targets': [
+                                       {
+                                           'queue': 'WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f',
+                                           'expression': 'task.language IN worker.languages'
+                                       }
+                                   ]
+                               },
+                               {
+                                   'expression': 'type == "support"',
+                                   'filter_friendly_name': 'Support',
+                                   'targets': [
+                                       {
+                                           'queue': 'WQe5eb317eb23500ade45087ea6522896c',
+                                           'expression': 'task.language IN worker.languages'
+                                       }
+                                   ]
+                               }
+                           ],
+                           'default_filter':
+                           {
+                               'queue': 'WQ05f810d2d130344fd56e3c91ece2e594'
+                           }
+                       }
+                   }
+            # marshal object
+            config = WorkflowConfig.json2obj(json.dumps(data))
+            self.assertEqual(3, len(config.task_routing.filters))
+            self.assertEqual(1, len(config.task_routing.default_filter))
+
+            # check that the configuration was marshaled to "friendly_name" and not "filter_friendly_name"
+            expected_config_data = {
+                                      "task_routing":
+                                      {
+                                          "default_filter":
+                                          {
+                                              "queue": "WQ05f810d2d130344fd56e3c91ece2e594"
+                                          },
+                                          "filters": [
+                                              {
+                                                  "expression": "type == \"sales\"",
+                                                  "friendly_name": "Sales",
+                                                  "targets": [
+                                                      {
+                                                          "expression": "task.language IN worker.languages",
+                                                          "queue": "WQec62de0e1148b8477f2e24579779c8b1"
+                                                      }
+                                                  ]
+                                              },
+                                              {
+                                                  "expression": "type == \"marketing\"",
+                                                  "friendly_name": "Marketing",
+                                                  "targets": [
+                                                      {
+                                                          "expression": "task.language IN worker.languages",
+                                                          "queue": "WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f"
+                                                      }
+                                                  ]
+                                              },
+                                              {
+                                                  "expression": "type == \"support\"",
+                                                  "friendly_name": "Support",
+                                                  "targets": [
+                                                      {
+                                                          "expression": "task.language IN worker.languages",
+                                                          "queue": "WQe5eb317eb23500ade45087ea6522896c"
+                                                      }
+                                                  ]
+                                              }
+                                          ]
+                                      }
+                                   }
+
+            expected_config_json = json.dumps(expected_config_data,
+                          sort_keys=True,
+                          indent=4)
+            # check that marshaling back stays as "friendly_name"
+            self.assertEqual(config.to_json(), expected_config_json)
+
+        def test_from_json_with_both_filter_and_friendly_name(self):
+            data = {
+                       'task_routing':
+                       {
+                           'filters': [
+                               {
+                                   'expression': 'type == "sales"',
+                                   'filter_friendly_name': "Sales",
+                                   'friendly_name': 'Sales2',
+                                   'targets': [
+                                       {
+
+                                           'queue': 'WQec62de0e1148b8477f2e24579779c8b1',
+                                           'expression': 'task.language IN worker.languages'
+                                       }
+                                   ]
+                               },
+                               {
+                                   'expression': 'type == "marketing"',
+                                   'filter_friendly_name': 'Marketing',
+                                   'friendly_name': 'Marketing2',
+                                   'targets': [
+                                       {
+                                           'queue': 'WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f',
+                                           'expression': 'task.language IN worker.languages'
+                                       }
+                                   ]
+                               },
+                               {
+                                   'expression': 'type == "support"',
+                                   'filter_friendly_name': 'Support',
+                                   'friendly_name': 'Support2',
+                                   'targets': [
+                                       {
+                                           'queue': 'WQe5eb317eb23500ade45087ea6522896c',
+                                           'expression': 'task.language IN worker.languages'
+                                       }
+                                   ]
+                               }
+                           ],
+                           'default_filter':
+                           {
+                               'queue': 'WQ05f810d2d130344fd56e3c91ece2e594'
+                           }
+                       }
+                   }
+            # marshal object
+            config = WorkflowConfig.json2obj(json.dumps(data))
+            self.assertEqual(3, len(config.task_routing.filters))
+            self.assertEqual(1, len(config.task_routing.default_filter))
+
+            # check that the configuration was marshaled to "friendly_name" and not "filter_friendly_name"
+            expected_config_data = {
+                                      "task_routing":
+                                      {
+                                          "default_filter":
+                                          {
+                                              "queue": "WQ05f810d2d130344fd56e3c91ece2e594"
+                                          },
+                                          "filters": [
+                                              {
+                                                  "expression": "type == \"sales\"",
+                                                  "friendly_name": "Sales",
+                                                  "targets": [
+                                                      {
+                                                          "expression": "task.language IN worker.languages",
+                                                          "queue": "WQec62de0e1148b8477f2e24579779c8b1"
+                                                      }
+                                                  ]
+                                              },
+                                              {
+                                                  "expression": "type == \"marketing\"",
+                                                  "friendly_name": "Marketing",
+                                                  "targets": [
+                                                      {
+                                                          "expression": "task.language IN worker.languages",
+                                                          "queue": "WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f"
+                                                      }
+                                                  ]
+                                              },
+                                              {
+                                                  "expression": "type == \"support\"",
+                                                  "friendly_name": "Support",
+                                                  "targets": [
+                                                      {
+                                                          "expression": "task.language IN worker.languages",
+                                                          "queue": "WQe5eb317eb23500ade45087ea6522896c"
+                                                      }
+                                                  ]
+                                              }
+                                          ]
+                                      }
+                                   }
+
+            expected_config_json = json.dumps(expected_config_data,
+                          sort_keys=True,
+                          indent=4)
+            # check that marshaling back stays as "friendly_name"
+            self.assertEqual(config.to_json(), expected_config_json)
+
+        def is_json(self, myjson):
+            try:
+                json.loads(myjson)
+            except ValueError as e:
+                print(e)
+                return False
+            return True
diff --git a/tests/test_access_token.py b/tests/test_access_token.py
new file mode 100644
index 0000000000..44bb96c036
--- /dev/null
+++ b/tests/test_access_token.py
@@ -0,0 +1,147 @@
+import time
+import unittest
+
+from datetime import datetime
+from nose.tools import assert_equal
+from twilio.jwt import decode
+from twilio.access_token import AccessToken, ConversationsGrant, IpMessagingGrant, VoiceGrant, VideoGrant
+
+ACCOUNT_SID = 'AC123'
+SIGNING_KEY_SID = 'SK123'
+
+
+# python2.6 support
+def assert_is_not_none(obj):
+    assert obj is not None, '%r is None' % obj
+
+
+def assert_in(obj1, obj2):
+    assert obj1 in obj2, '%r is not in %r' % (obj1, obj2)
+
+
+def assert_greater_equal(obj1, obj2):
+    assert obj1 > obj2, '%r is not greater than or equal to %r' % (obj1, obj2)
+
+
+class AccessTokenTest(unittest.TestCase):
+    def _validate_claims(self, payload):
+        assert_equal(SIGNING_KEY_SID, payload['iss'])
+        assert_equal(ACCOUNT_SID, payload['sub'])
+
+        assert_is_not_none(payload['exp'])
+        assert_is_not_none(payload['jti'])
+        assert_is_not_none(payload['grants'])
+
+        assert_greater_equal(payload['exp'], int(time.time()))
+
+        assert_in(payload['iss'], payload['jti'])
+
+    def test_empty_grants(self):
+        scat = AccessToken(ACCOUNT_SID, SIGNING_KEY_SID, 'secret')
+        token = str(scat)
+
+        assert_is_not_none(token)
+        payload = decode(token, 'secret')
+        self._validate_claims(payload)
+        assert_equal({}, payload['grants'])
+
+    def test_nbf(self):
+        now = int(time.mktime(datetime.now().timetuple()))
+        scat = AccessToken(ACCOUNT_SID, SIGNING_KEY_SID, 'secret', nbf=now)
+        token = str(scat)
+
+        assert_is_not_none(token)
+        payload = decode(token, 'secret')
+        self._validate_claims(payload)
+        assert_equal(now, payload['nbf'])
+
+    def test_identity(self):
+        scat = AccessToken(ACCOUNT_SID, SIGNING_KEY_SID, 'secret', identity='test@twilio.com')
+        token = str(scat)
+
+        assert_is_not_none(token)
+        payload = decode(token, 'secret')
+        self._validate_claims(payload)
+        assert_equal({
+            'identity': 'test@twilio.com'
+        }, payload['grants'])
+
+    def test_conversations_grant(self):
+        scat = AccessToken(ACCOUNT_SID, SIGNING_KEY_SID, 'secret')
+        scat.add_grant(ConversationsGrant(configuration_profile_sid='CP123'))
+
+        token = str(scat)
+        assert_is_not_none(token)
+        payload = decode(token, 'secret')
+        self._validate_claims(payload)
+        assert_equal(1, len(payload['grants']))
+        assert_equal({
+            'configuration_profile_sid': 'CP123'
+        }, payload['grants']['rtc'])
+
+    def test_ip_messaging_grant(self):
+        scat = AccessToken(ACCOUNT_SID, SIGNING_KEY_SID, 'secret')
+        scat.add_grant(IpMessagingGrant(service_sid='IS123', push_credential_sid='CR123'))
+
+        token = str(scat)
+        assert_is_not_none(token)
+        payload = decode(token, 'secret')
+        self._validate_claims(payload)
+        assert_equal(1, len(payload['grants']))
+        assert_equal({
+            'service_sid': 'IS123',
+            'push_credential_sid': 'CR123'
+        }, payload['grants']['ip_messaging'])
+
+    def test_video_grant(self):
+        scat = AccessToken(ACCOUNT_SID, SIGNING_KEY_SID, 'secret')
+        scat.add_grant(VideoGrant(configuration_profile_sid='CP123'))
+
+        token = str(scat)
+        assert_is_not_none(token)
+        payload = decode(token, 'secret')
+        self._validate_claims(payload)
+        assert_equal(1, len(payload['grants']))
+        assert_equal({
+            'configuration_profile_sid': 'CP123'
+        }, payload['grants']['video'])
+
+    def test_grants(self):
+        scat = AccessToken(ACCOUNT_SID, SIGNING_KEY_SID, 'secret')
+        scat.add_grant(ConversationsGrant())
+        scat.add_grant(IpMessagingGrant())
+        scat.add_grant(VideoGrant())
+
+        token = str(scat)
+        assert_is_not_none(token)
+        payload = decode(token, 'secret')
+        self._validate_claims(payload)
+        assert_equal(3, len(payload['grants']))
+        assert_equal({}, payload['grants']['rtc'])
+        assert_equal({}, payload['grants']['ip_messaging'])
+        assert_equal({}, payload['grants']['video'])
+
+    def test_programmable_voice_grant(self):
+        grant = VoiceGrant(
+            outgoing_application_sid='AP123',
+            outgoing_application_params={
+                'foo': 'bar'
+            }
+        )
+
+        scat = AccessToken(ACCOUNT_SID, SIGNING_KEY_SID, 'secret')
+        scat.add_grant(grant)
+
+        token = str(scat)
+        assert_is_not_none(token)
+        payload = decode(token, 'secret')
+        self._validate_claims(payload)
+        assert_equal(1, len(payload['grants']))
+        assert_equal({
+            'outgoing': {
+                'application_sid': 'AP123',
+                'params': {
+                    'foo': 'bar'
+                }
+            }
+        }, payload['grants']['voice'])
diff --git a/tests/test_accounts.py b/tests/test_accounts.py
index caacfb5baa..d201312713 100644
--- a/tests/test_accounts.py
+++ b/tests/test_accounts.py
@@ -18,7 +18,7 @@ def test_usage_records_subresource(self, request):
         account = Account(mock, 'AC123')
         account.load_subresources()
         records = account.usage_records.list()
-        self.assertEquals(len(records), 2)
+        self.assertEqual(len(records), 2)
 
     @patch("twilio.rest.resources.base.make_twilio_request")
     def test_usage_triggers_subresource(self, request):
@@ -30,4 +30,4 @@ def test_usage_triggers_subresource(self, request):
         account = Account(mock, 'AC123')
         account.load_subresources()
         triggers = account.usage_triggers.list()
-        self.assertEquals(len(triggers), 2)
+        self.assertEqual(len(triggers), 2)
diff --git a/tests/test_keys.py b/tests/test_keys.py
new file mode 100644
index 0000000000..e57ad65ea1
--- /dev/null
+++ b/tests/test_keys.py
@@ -0,0 +1,75 @@
+from mock import patch, Mock
+from twilio.rest.resources.keys import Keys, Key
+from tests.tools import create_mock_json
+
+ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+AUTH = (ACCOUNT_SID, "token")
+BASE_URL = "https://api.twilio.com/2010-04-01/Accounts/{0}".format(ACCOUNT_SID)
+KEY_SID = "SKaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+
+list_resource = Keys(BASE_URL, AUTH)
+
+
+@patch("twilio.rest.resources.base.make_twilio_request")
+def test_get_key(mock):
+    resp = create_mock_json("tests/resources/keys_instance.json")
+    mock.return_value = resp
+
+    url = BASE_URL + "/Keys/{0}".format(KEY_SID)
+    list_resource.get(KEY_SID)
+
+    mock.assert_called_with("GET", url, auth=AUTH, use_json_extension=True)
+
+
+@patch("twilio.rest.resources.base.make_twilio_request")
+def test_create_key(mock):
+    resp = create_mock_json("tests/resources/keys_instance.json")
+    resp.status_code = 201
+    mock.return_value = resp
+
+    url = BASE_URL + "/Keys"
+    list_resource.create(friendly_name="Fuzzy Lumpkins' SigningKey")
+    params = {
+        'FriendlyName': "Fuzzy Lumpkins' SigningKey"
+    }
+
+    mock.assert_called_with("POST", url, data=params, auth=AUTH, use_json_extension=True)
+
+
+@patch("twilio.rest.resources.base.make_twilio_request")
+def test_update_key(mock):
+    resp = create_mock_json("tests/resources/keys_instance.json")
+    mock.return_value = resp
+
+    url = BASE_URL + "/Keys/{0}".format(KEY_SID)
+    list_resource.update(sid=KEY_SID, friendly_name="Fuzzy Lumpkins' SigningKey")
+    params = {
+        'FriendlyName': "Fuzzy Lumpkins' SigningKey"
+    }
+
+    mock.assert_called_with("POST", url, data=params, auth=AUTH, use_json_extension=True)
+
+
+@patch("twilio.rest.resources.base.Resource.request")
+def test_delete_key(mock):
+    resp = Mock()
+    resp.content = ""
+    resp.status_code = 204
+    mock.return_value = resp, {}
+
+    key = Key(list_resource, KEY_SID)
+    key.delete()
+
+    url = BASE_URL + "/Keys/{0}".format(KEY_SID)
+    mock.assert_called_with("DELETE", url)
+
+
+@patch("twilio.rest.resources.base.make_twilio_request")
+def test_list_keys(mock):
+    resp = create_mock_json("tests/resources/keys_list.json")
+    mock.return_value = resp
+
+    url = BASE_URL + "/Keys"
+    list_resource.list()
+
+    mock.assert_called_with("GET", url, params={}, auth=AUTH, use_json_extension=True)
diff --git a/tests/test_phone_numbers.py b/tests/test_phone_numbers.py
index 18e88b8caf..6f98b5769b 100644
--- a/tests/test_phone_numbers.py
+++ b/tests/test_phone_numbers.py
@@ -79,7 +79,7 @@ def test_base_uri(self):
             entry = json.load(f)
             resource.load(entry)
 
-        self.assertEquals(resource.parent.base_uri,
+        self.assertEqual(resource.parent.base_uri,
             ("https://api.twilio.com/2010-04-01/Accounts/AC4bf2dafbed59a573"
              "3d2c1c1c69a83a28"))
 
diff --git a/tests/test_recordings.py b/tests/test_recordings.py
index 04960af112..27993d5cc9 100644
--- a/tests/test_recordings.py
+++ b/tests/test_recordings.py
@@ -1,6 +1,6 @@
 from datetime import date
 from mock import patch
-from nose.tools import raises, assert_equals, assert_true
+from nose.tools import raises, assert_equal, assert_true
 
 from tests.tools import create_mock_json
 from twilio.rest.resources import Recordings, Recording
@@ -27,6 +27,24 @@ def test_paging(mock):
                             use_json_extension=True)
 
 
+@patch("twilio.rest.resources.base.make_twilio_request")
+def test_paging_iter(mock):
+    resp = create_mock_json("tests/resources/recordings_list.json")
+    mock.return_value = resp
+
+    uri = "%s/Recordings" % (BASE_URI)
+
+    next(recordings.iter(before=date(2010, 12, 5)))
+    exp_params = {'DateCreated<': '2010-12-05'}
+    mock.assert_called_with("GET", uri, params=exp_params, auth=AUTH,
+                            use_json_extension=True)
+
+    next(recordings.iter(after=date(2012, 12, 7)))
+    exp_params = {'DateCreated>': '2012-12-07'}
+    mock.assert_called_with("GET", uri, params=exp_params, auth=AUTH,
+                            use_json_extension=True)
+
+
 @patch("twilio.rest.resources.base.make_twilio_request")
 def test_get(mock):
     resp = create_mock_json("tests/resources/recordings_instance.json")
@@ -39,7 +57,7 @@ def test_get(mock):
                             use_json_extension=True)
 
     truri = "%s/Recordings/%s/Transcriptions" % (BASE_URI, RE_SID)
-    assert_equals(r.transcriptions.uri, truri)
+    assert_equal(r.transcriptions.uri, truri)
 
 
 @patch("twilio.rest.resources.base.make_twilio_request")
diff --git a/tests/test_twiml.py b/tests/test_twiml.py
index 7bab2ad1e9..c022baeda0 100644
--- a/tests/test_twiml.py
+++ b/tests/test_twiml.py
@@ -367,16 +367,16 @@ def setUp(self):
             # parse twiml XML string with Element Tree and inspect
             # structure
             tree = ET.fromstring(xml)
-            self.conf = tree.find(".//Queue")
+            self.queue = tree.find(".//Queue")
 
-    def test_conf_text(self):
-        self.assertEqual(self.conf.text.strip(), "TestQueueAttribute")
+    def test_queue_text(self):
+        self.assertEqual(self.queue.text.strip(), "TestQueueAttribute")
 
-    def test_conf_waiturl(self):
-        self.assertEqual(self.conf.get('url'), "")
+    def test_queue_waiturl(self):
+        self.assertEqual(self.queue.get('url'), "")
 
-    def test_conf_method(self):
-        self.assertEqual(self.conf.get('method'), "GET")
+    def test_queue_method(self):
+        self.assertEqual(self.queue.get('method'), "GET")
 
 
 class TestEnqueue(TwilioTest):
@@ -390,22 +390,53 @@ def setUp(self):
         # parse twiml XML string with Element Tree and inspect
         # structure
         tree = ET.fromstring(xml)
-        self.conf = tree.find("./Enqueue")
+        self.enqueue = tree.find("./Enqueue")
 
-    def test_conf_text(self):
-        self.assertEqual(self.conf.text.strip(), "TestEnqueueAttribute")
+    def test_enqueue_text(self):
+        self.assertEqual(self.enqueue.text.strip(), "TestEnqueueAttribute")
 
-    def test_conf_waiturl(self):
-        self.assertEqual(self.conf.get('waitUrl'), "wait")
+    def test_enqueue_waiturl(self):
+        self.assertEqual(self.enqueue.get('waitUrl'), "wait")
+
+    def test_enqueue_method(self):
+        self.assertEqual(self.enqueue.get('method'), "GET")
+
+    def test_enqueue_action(self):
+        self.assertEqual(self.enqueue.get('action'), "act")
+
+    def test_enqueue_waitmethod(self):
+        self.assertEqual(self.enqueue.get('waitUrlMethod'), "POST")
+
+
+class TestEnqueueTask(TwilioTest):
+
+    def setUp(self):
+        r = Response()
+        with r.enqueue(None, workflowSid="Workflow1") as e:
+            e.task('{"selected_language":"en"}', priority="10", timeout="50")
+
+        xml = r.toxml()
+
+        # parse twiml XML string with Element Tree and inspect
+        # structure
+        tree = ET.fromstring(xml)
+        self.enqueue = tree.find("./Enqueue")
+        self.task = self.enqueue.find(".//Task")
+
+    def test_found_task(self):
+        self.assertNotEqual(None, self.task)
+
+    def test_enqueue_workflow_sid(self):
+        self.assertEqual(self.enqueue.get('workflowSid'), "Workflow1")
 
-    def test_conf_method(self):
-        self.assertEqual(self.conf.get('method'), "GET")
+    def test_enqueue_task_attributes(self):
+        self.assertEqual(self.task.text.strip(), '{"selected_language":"en"}')
 
-    def test_conf_action(self):
-        self.assertEqual(self.conf.get('action'), "act")
+    def test_enqueue_task_priority(self):
+        self.assertEqual(self.task.get('priority'), "10")
 
-    def test_conf_waitmethod(self):
-        self.assertEqual(self.conf.get('waitUrlMethod'), "POST")
+    def test_enqueue_task_timeout(self):
+        self.assertEqual(self.task.get('timeout'), "50")
 
 
 class TestDial(TwilioTest):
diff --git a/tests/trunking/test_credential_lists.py b/tests/trunking/test_credential_lists.py
new file mode 100644
index 0000000000..f5b9594601
--- /dev/null
+++ b/tests/trunking/test_credential_lists.py
@@ -0,0 +1,101 @@
+import unittest
+from mock import Mock, patch
+from nose.tools import assert_equal, assert_true
+from tests.tools import create_mock_json
+from twilio.rest.resources.trunking.credential_lists import CredentialLists
+
+ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+AUTH = (ACCOUNT_SID, "auth_token")
+BASE_URI = "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111"
+
+
+class CredentialListsTest(unittest.TestCase):
+
+    @patch('twilio.rest.resources.base.make_twilio_request')
+    def test_get_credential_lists(self, request):
+        resp = create_mock_json('tests/resources/trunking/credential_lists_list.json')
+        resp.status_code = 200
+        request.return_value = resp
+
+        credential_lists = CredentialLists(BASE_URI, AUTH)
+        result = credential_lists.list()
+
+        assert_equal(len(result), 1)
+        assert_equal(result[0].sid, 'CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
+        assert_equal(result[0].account_sid, 'ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
+        assert_equal(result[0].trunk_sid, "TK11111111111111111111111111111111")
+        assert_equal(result[0].friendly_name, "Test list")
+        assert_equal(result[0].url, "{0}/CredentialLists/CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI))
+
+        request.assert_called_with(
+            "GET",
+            "{0}/CredentialLists".format(BASE_URI),
+            auth=AUTH,
+            params={},
+            use_json_extension=False,
+        )
+
+    @patch('twilio.rest.resources.base.make_twilio_request')
+    def test_get_credential_lists_instance(self, request):
+        resp = create_mock_json('tests/resources/trunking/credential_lists_instance.json')
+        resp.status_code = 200
+        request.return_value = resp
+
+        credential_lists = CredentialLists(BASE_URI, AUTH)
+        result = credential_lists.get('CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
+
+        assert_equal(result.sid, "CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
+        assert_equal(result.account_sid, "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
+        assert_equal(result.trunk_sid, "TK11111111111111111111111111111111")
+        assert_equal(result.friendly_name, "Test")
+        assert_equal(result.url, "{0}/CredentialLists/CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI))
+
+        request.assert_called_with(
+            "GET",
+            "{0}/CredentialLists/CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI),
+            auth=AUTH,
+            use_json_extension=False
+        )
+
+    @patch('twilio.rest.resources.base.make_twilio_request')
+    def test_create_credential_lists_instance(self, request):
+        resp = create_mock_json('tests/resources/trunking/credential_lists_instance.json')
+        resp.status_code = 201
+        request.return_value = resp
+
+        credential_lists = CredentialLists(BASE_URI, AUTH)
+        result = credential_lists.create('CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
+
+        assert_equal(result.sid, "CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
+        assert_equal(result.account_sid, "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
+        assert_equal(result.trunk_sid, "TK11111111111111111111111111111111")
+        assert_equal(result.friendly_name, "Test")
+        assert_equal(result.url, "{0}/CredentialLists/CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI))
+
+        data_dict = dict()
+        data_dict['CredentialListSid'] = 'CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
+        request.assert_called_with(
+            "POST",
+            "{0}/CredentialLists".format(BASE_URI),
+            auth=AUTH,
+            use_json_extension=False,
+            data=data_dict,
+        )
+
+    @patch('twilio.rest.resources.base.make_twilio_request')
+    def test_delete_credential_lists_instance(self, request):
+        resp = Mock()
+        resp.status_code = 204
+        request.return_value = resp
+
+        credential_lists = CredentialLists(BASE_URI, AUTH)
+        result = credential_lists.delete('CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
+
+        assert_true(result)
+
+        request.assert_called_with(
+            "DELETE",
+            "{0}/CredentialLists/CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI),
+            auth=AUTH,
+            use_json_extension=False
+        )
diff --git a/tests/trunking/test_ip_access_control_lists.py b/tests/trunking/test_ip_access_control_lists.py
new file mode 100644
index 0000000000..59dfa9f5f3
--- /dev/null
+++ b/tests/trunking/test_ip_access_control_lists.py
@@ -0,0 +1,103 @@
+import unittest
+from mock import Mock, patch
+from nose.tools import assert_equal, assert_true
+from tests.tools import create_mock_json
+from twilio.rest.resources.trunking.ip_access_control_lists import (
+    IpAccessControlLists
+)
+
+ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+AUTH = (ACCOUNT_SID, "auth_token")
+BASE_URI = "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111"
+
+
+class IpAccessControlListsTest(unittest.TestCase):
+
+    @patch('twilio.rest.resources.base.make_twilio_request')
+    def test_get_ip_access_control_lists(self, request):
+        resp = create_mock_json('tests/resources/trunking/ip_access_control_lists_list.json')
+        resp.status_code = 200
+        request.return_value = resp
+
+        ip_access_control_lists = IpAccessControlLists(BASE_URI, AUTH)
+        result = ip_access_control_lists.list()
+
+        assert_equal(len(result), 1)
+        assert_equal(result[0].sid, 'ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
+        assert_equal(result[0].account_sid, 'ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
+        assert_equal(result[0].trunk_sid, "TK11111111111111111111111111111111")
+        assert_equal(result[0].friendly_name, "Test")
+        assert_equal(result[0].url, "{0}/IpAccessControlLists/ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI))
+
+        request.assert_called_with(
+            "GET",
+            "{0}/IpAccessControlLists".format(BASE_URI),
+            auth=AUTH,
+            params={},
+            use_json_extension=False,
+        )
+
+    @patch('twilio.rest.resources.base.make_twilio_request')
+    def test_get_ip_access_control_lists_instance(self, request):
+        resp = create_mock_json('tests/resources/trunking/ip_access_control_lists_instance.json')
+        resp.status_code = 200
+        request.return_value = resp
+
+        ip_access_control_lists = IpAccessControlLists(BASE_URI, AUTH)
+        result = ip_access_control_lists.get('ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
+
+        assert_equal(result.sid, "ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
+        assert_equal(result.account_sid, "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
+        assert_equal(result.trunk_sid, "TK11111111111111111111111111111111")
+        assert_equal(result.friendly_name, "Test")
+        assert_equal(result.url, "{0}/IpAccessControlLists/ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI))
+
+        request.assert_called_with(
+            "GET",
+            "{0}/IpAccessControlLists/ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI),
+            auth=AUTH,
+            use_json_extension=False
+        )
+
+    @patch('twilio.rest.resources.base.make_twilio_request')
+    def test_associate_ip_access_control_lists_instance(self, request):
+        resp = create_mock_json('tests/resources/trunking/ip_access_control_lists_instance.json')
+        resp.status_code = 201
+        request.return_value = resp
+
+        ip_access_control_lists = IpAccessControlLists(BASE_URI, AUTH)
+        result = ip_access_control_lists.create('ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
+
+        assert_equal(result.sid, "ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
+        assert_equal(result.account_sid, "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
+        assert_equal(result.trunk_sid, "TK11111111111111111111111111111111")
+        assert_equal(result.friendly_name, "Test")
+        assert_equal(result.url, "{0}/IpAccessControlLists/ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI))
+
+        data_dict = dict()
+        data_dict['IpAccessControlListSid'] = 'ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
+        request.assert_called_with(
+            "POST",
+            "{0}/IpAccessControlLists".format(BASE_URI),
+            auth=AUTH,
+            use_json_extension=False,
+            data=data_dict,
+        )
+
+    @patch('twilio.rest.resources.base.make_twilio_request')
+    def test_disassociate_ip_access_control_lists_instance(self, request):
+        resp = Mock()
+        resp.status_code = 204
+        request.return_value = resp
+
+        ip_access_control_lists = IpAccessControlLists(BASE_URI, AUTH)
+        result = ip_access_control_lists.delete('ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
+
+        assert_true(result)
+
+        request.assert_called_with(
+            "DELETE",
+            "{0}/IpAccessControlLists/ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI),
+            auth=AUTH,
+            use_json_extension=False
+        )
diff --git a/tests/trunking/test_origination_urls.py b/tests/trunking/test_origination_urls.py
new file mode 100644
index 0000000000..3da34feb30
--- /dev/null
+++ b/tests/trunking/test_origination_urls.py
@@ -0,0 +1,150 @@
+import unittest
+from mock import Mock, patch
+from nose.tools import assert_equal, assert_true
+from tests.tools import create_mock_json
+from twilio.rest.resources.trunking.origination_urls import (
+    OriginationUrls
+)
+
+ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+AUTH = (ACCOUNT_SID, "auth_token")
+BASE_URI = "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111"
+
+
+class OriginationUrlsTest(unittest.TestCase):
+
+    @patch('twilio.rest.resources.base.make_twilio_request')
+    def test_get_origination_urls_lists(self, request):
+        resp = create_mock_json('tests/resources/trunking/origination_urls_list.json')
+        resp.status_code = 200
+        request.return_value = resp
+
+        origination_urls = OriginationUrls(BASE_URI, AUTH)
+        result = origination_urls.list()
+
+        assert_equal(len(result), 1)
+        assert_equal(result[0].sid, 'OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
+        assert_equal(result[0].account_sid, 'ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
+        assert_equal(result[0].trunk_sid, "TK11111111111111111111111111111111")
+        assert_equal(result[0].friendly_name, "Name")
+        assert_equal(result[0].sip_url, "sip:169.10.1.35")
+        assert_equal(result[0].weight, 10)
+        assert_equal(result[0].priority, 20)
+        assert_true(result[0].enabled)
+        assert_equal(result[0].url, "{0}/OriginationUrls/OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI))
+
+        request.assert_called_with(
+            "GET",
+            "{0}/OriginationUrls".format(BASE_URI),
+            auth=AUTH,
+            params={},
+            use_json_extension=False,
+        )
+
+    @patch('twilio.rest.resources.base.make_twilio_request')
+    def test_get_origination_urls_instance(self, request):
+        resp = create_mock_json('tests/resources/trunking/origination_urls_instance.json')
+        resp.status_code = 200
+        request.return_value = resp
+
+        origination_urls = OriginationUrls(BASE_URI, AUTH)
+        result = origination_urls.get('OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
+
+        assert_equal(result.sid, "OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
+        assert_equal(result.account_sid, "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
+        assert_equal(result.trunk_sid, "TK11111111111111111111111111111111")
+        assert_equal(result.friendly_name, "Name")
+        assert_equal(result.sip_url, "sip:169.10.1.35")
+        assert_equal(result.weight, 10)
+        assert_equal(result.priority, 20)
+        assert_true(result.enabled)
+        assert_equal(result.url, "{0}/OriginationUrls/OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI))
+
+        request.assert_called_with(
+            "GET",
+            "{0}/OriginationUrls/OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI),
+            auth=AUTH,
+            use_json_extension=False
+        )
+
+    @patch('twilio.rest.resources.base.make_twilio_request')
+    def test_create_origination_urls_instance(self, request):
+        resp = create_mock_json('tests/resources/trunking/origination_urls_instance.json')
+        resp.status_code = 201
+        request.return_value = resp
+
+        origination_urls = OriginationUrls(BASE_URI, AUTH)
+        result = origination_urls.create('Name', 'sip:169.10.1.35')
+
+        assert_equal(result.sid, "OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
+        assert_equal(result.account_sid, "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
+        assert_equal(result.trunk_sid, "TK11111111111111111111111111111111")
+        assert_equal(result.friendly_name, "Name")
+        assert_equal(result.sip_url, "sip:169.10.1.35")
+        assert_equal(result.weight, 10)
+        assert_equal(result.priority, 20)
+        assert_true(result.enabled)
+        assert_equal(result.url, "{0}/OriginationUrls/OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI))
+
+        data_dict = dict()
+        data_dict['FriendlyName'] = 'Name'
+        data_dict['SipUrl'] = 'sip:169.10.1.35'
+        data_dict['Priority'] = 10
+        data_dict['Weight'] = 10
+        data_dict['Enabled'] = 'true'
+
+        request.assert_called_with(
+            "POST",
+            "{0}/OriginationUrls".format(BASE_URI),
+            auth=AUTH,
+            use_json_extension=False,
+            data=data_dict,
+        )
+
+    @patch('twilio.rest.resources.base.make_twilio_request')
+    def test_update_origination_urls_instance(self, request):
+        resp = create_mock_json('tests/resources/trunking/origination_urls_instance.json')
+        resp.status_code = 200
+        request.return_value = resp
+
+        origination_urls = OriginationUrls(BASE_URI, AUTH)
+        result = origination_urls.update('OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', {'Priority': 10})
+
+        assert_equal(result.sid, "OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
+        assert_equal(result.account_sid, "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
+        assert_equal(result.trunk_sid, "TK11111111111111111111111111111111")
+        assert_equal(result.friendly_name, "Name")
+        assert_equal(result.sip_url, "sip:169.10.1.35")
+        assert_equal(result.weight, 10)
+        assert_equal(result.priority, 20)
+        assert_true(result.enabled)
+        assert_equal(result.url, "{0}/OriginationUrls/OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI))
+
+        data_dict = dict()
+        data_dict['Priority'] = 10
+
+        request.assert_called_with(
+            "POST",
+            "{0}/OriginationUrls/OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI),
+            auth=AUTH,
+            use_json_extension=False,
+            data=data_dict
+        )
+
+    @patch('twilio.rest.resources.base.make_twilio_request')
+    def test_delete_origination_urls_instance(self, request):
+        resp = Mock()
+        resp.status_code = 204
+        request.return_value = resp
+
+        origination_urls = OriginationUrls(BASE_URI, AUTH)
+        result = origination_urls.delete('OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
+
+        assert_true(result)
+
+        request.assert_called_with(
+            "DELETE",
+            "{0}/OriginationUrls/OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI),
+            auth=AUTH,
+            use_json_extension=False
+        )
diff --git a/tests/trunking/test_phone_numbers.py b/tests/trunking/test_phone_numbers.py
new file mode 100644
index 0000000000..8ed54ab33a
--- /dev/null
+++ b/tests/trunking/test_phone_numbers.py
@@ -0,0 +1,158 @@
+import unittest
+from mock import Mock, patch
+from nose.tools import assert_equal, assert_true
+from tests.tools import create_mock_json
+from twilio.rest.resources.trunking.phone_numbers import (
+    PhoneNumbers
+)
+
+API_BASE_URI = "https://api.twilio.com/2010-04-01/Accounts"
+ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+PHONE_NUMBERS_BASE_URI = "{0}/{1}/{2}".format(API_BASE_URI, ACCOUNT_SID,
+                                              "IncomingPhoneNumbers")
+AUTH = (ACCOUNT_SID, "auth_token")
+BASE_URI = "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111"
+
+
+class PhoneNumbersTest(unittest.TestCase):
+    @patch('twilio.rest.resources.base.make_twilio_request')
+    def test_get_phone_numbers_lists(self, request):
+        resp = create_mock_json(
+            'tests/resources/trunking/phone_numbers_list.json')
+        resp.status_code = 200
+        request.return_value = resp
+
+        phone_numbers = PhoneNumbers(BASE_URI, AUTH)
+        result = phone_numbers.list()
+
+        assert_equal(len(result), 1)
+        assert_equal(result[0].sid, 'PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
+        assert_equal(result[0].account_sid,
+                     'ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
+        assert_equal(result[0].trunk_sid, "TK11111111111111111111111111111111")
+        assert_equal(result[0].friendly_name, "Name")
+        assert_equal(result[0].phone_number, "+14158675309")
+        assert_equal(result[0].api_version, "2010-04-01")
+        assert_equal(result[0].voice_caller_id_lookup, False)
+        assert_equal(result[0].voice_fallback_method, "POST")
+        assert_equal(result[0].status_callback_method, "POST")
+        assert_equal(result[0].sms_url,
+                     "https://demo.twilio.com/welcome/sms/reply/")
+        assert_equal(result[0].sms_method, "POST")
+        assert_equal(result[0].sms_fallback_method, "POST")
+        assert_equal(result[0].address_requirements, "none")
+        assert_equal(result[0].beta, False)
+        assert_equal(result[0].url,
+                     "{0}/PhoneNumbers/PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".
+                     format(BASE_URI))
+        assert_equal(result[0].links['phone_number'],
+                     "{0}/{1}".format(PHONE_NUMBERS_BASE_URI,
+                                      "PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"))
+        request.assert_called_with(
+            "GET",
+            "{0}/PhoneNumbers".format(BASE_URI),
+            auth=AUTH,
+            params={},
+            use_json_extension=False,
+        )
+
+    @patch('twilio.rest.resources.base.make_twilio_request')
+    def test_get_phone_numbers_instance(self, request):
+        resp = create_mock_json(
+            'tests/resources/trunking/phone_numbers_instance.json')
+        resp.status_code = 200
+        request.return_value = resp
+
+        phone_numbers = PhoneNumbers(BASE_URI, AUTH)
+        result = phone_numbers.get('PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
+
+        assert_equal(result.sid, 'PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
+        assert_equal(result.account_sid, 'ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
+        assert_equal(result.trunk_sid, "TK11111111111111111111111111111111")
+        assert_equal(result.friendly_name, "Name")
+        assert_equal(result.phone_number, "+14158675309")
+        assert_equal(result.api_version, "2010-04-01")
+        assert_equal(result.voice_caller_id_lookup, False)
+        assert_equal(result.voice_fallback_method, "POST")
+        assert_equal(result.status_callback_method, "POST")
+        assert_equal(result.sms_url,
+                     "https://demo.twilio.com/welcome/sms/reply/")
+        assert_equal(result.sms_method, "POST")
+        assert_equal(result.sms_fallback_method, "POST")
+        assert_equal(result.address_requirements, "none")
+        assert_equal(result.beta, False)
+        assert_equal(result.url,
+                     "{0}/PhoneNumbers/PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(
+                         BASE_URI))
+        assert_equal(result.links['phone_number'],
+                     "{0}/{1}".format(PHONE_NUMBERS_BASE_URI,
+                                      "PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"))
+
+        request.assert_called_with(
+            "GET",
+            "{0}/PhoneNumbers/PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(
+                BASE_URI),
+            auth=AUTH,
+            use_json_extension=False
+        )
+
+    @patch('twilio.rest.resources.base.make_twilio_request')
+    def test_associate_phone_numbers_instance(self, request):
+        resp = create_mock_json(
+            'tests/resources/trunking/phone_numbers_instance.json')
+        resp.status_code = 201
+        request.return_value = resp
+
+        phone_numbers = PhoneNumbers(BASE_URI, AUTH)
+        result = phone_numbers.create('PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
+
+        assert_equal(result.sid, 'PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
+        assert_equal(result.account_sid, 'ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
+        assert_equal(result.trunk_sid, "TK11111111111111111111111111111111")
+        assert_equal(result.friendly_name, "Name")
+        assert_equal(result.phone_number, "+14158675309")
+        assert_equal(result.api_version, "2010-04-01")
+        assert_equal(result.voice_caller_id_lookup, False)
+        assert_equal(result.voice_fallback_method, "POST")
+        assert_equal(result.status_callback_method, "POST")
+        assert_equal(result.sms_url,
+                     "https://demo.twilio.com/welcome/sms/reply/")
+        assert_equal(result.sms_method, "POST")
+        assert_equal(result.sms_fallback_method, "POST")
+        assert_equal(result.address_requirements, "none")
+        assert_equal(result.beta, False)
+        assert_equal(result.url,
+                     "{0}/PhoneNumbers/PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(
+                         BASE_URI))
+        assert_equal(result.links['phone_number'],
+                     "{0}/{1}".format(PHONE_NUMBERS_BASE_URI,
+                                      "PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"))
+
+        data_dict = dict()
+        data_dict['PhoneNumberSid'] = 'PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
+        request.assert_called_with(
+            "POST",
+            "{0}/PhoneNumbers".format(BASE_URI),
+            auth=AUTH,
+            use_json_extension=False,
+            data=data_dict,
+        )
+
+    @patch('twilio.rest.resources.base.make_twilio_request')
+    def test_disassociate_phone_numbers_instance(self, request):
+        resp = Mock()
+        resp.status_code = 204
+        request.return_value = resp
+
+        phone_numbers = PhoneNumbers(BASE_URI, AUTH)
+        result = phone_numbers.delete('PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
+
+        assert_true(result)
+
+        request.assert_called_with(
+            "DELETE",
+            "{0}/PhoneNumbers/PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(
+                BASE_URI),
+            auth=AUTH,
+            use_json_extension=False
+        )
diff --git a/tests/trunking/test_trunks.py b/tests/trunking/test_trunks.py
new file mode 100644
index 0000000000..6ce82d191e
--- /dev/null
+++ b/tests/trunking/test_trunks.py
@@ -0,0 +1,208 @@
+import unittest
+from mock import Mock, patch
+from nose.tools import assert_equal, assert_true
+from tests.tools import create_mock_json
+from twilio.rest.resources.trunking.trunks import (
+    Trunks
+)
+
+ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+AUTH = (ACCOUNT_SID, "auth_token")
+BASE_URI = "https://trunking.twilio.com/v1"
+TRUNK_SID = "TK11111111111111111111111111111111"
+
+
+class TrunksTest(unittest.TestCase):
+
+    @patch('twilio.rest.resources.base.make_twilio_request')
+    def test_get_trunks_lists(self, request):
+        resp = create_mock_json('tests/resources/trunking/trunks_list.json')
+        resp.status_code = 200
+        request.return_value = resp
+
+        trunks = Trunks(BASE_URI, AUTH)
+        result = trunks.list()
+
+        assert_equal(len(result), 1)
+        assert_equal(result[0].sid, 'TK11111111111111111111111111111111')
+        assert_equal(result[0].account_sid, 'ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
+        assert_equal(result[0].domain_name, "test-trunk.pstn.twilio.com")
+        assert_equal(result[0].friendly_name, "Test")
+        assert_equal(result[0].recording,
+                     {"trim": "do-not-trim",
+                      "mode": "record-from-ringing"})
+
+        assert_equal(result[0].auth_type, "CREDENTIAL_LIST")
+        assert_equal(result[0].auth_type_set, ["CREDENTIAL_LIST"])
+        TRUNK_INSTANCE_BASE_URI = "{0}/{1}/{2}".format(BASE_URI, "Trunks",
+                                                       TRUNK_SID)
+
+        assert_equal(result[0].url, TRUNK_INSTANCE_BASE_URI)
+
+        assert_equal(result[0].links['origination_urls'],
+                     "{0}/OriginationUrls".format(TRUNK_INSTANCE_BASE_URI))
+        assert_equal(result[0].links['credential_lists'],
+                     "{0}/CredentialLists".format(TRUNK_INSTANCE_BASE_URI))
+        assert_equal(result[0].links['ip_access_control_lists'],
+                     "{0}/IpAccessControlLists".format(TRUNK_INSTANCE_BASE_URI))
+        assert_equal(result[0].links['phone_numbers'],
+                     "{0}/PhoneNumbers".format(TRUNK_INSTANCE_BASE_URI))
+
+        request.assert_called_with(
+            "GET",
+            "{0}/Trunks".format(BASE_URI),
+            auth=AUTH,
+            params={},
+            use_json_extension=False,
+        )
+
+    @patch('twilio.rest.resources.base.make_twilio_request')
+    def test_get_trunks_instance(self, request):
+        resp = create_mock_json('tests/resources/trunking/trunks_instance.json')
+        resp.status_code = 200
+        request.return_value = resp
+
+        trunks = Trunks(BASE_URI, AUTH)
+        result = trunks.get('TK11111111111111111111111111111111')
+
+        assert_equal(result.sid, 'TK11111111111111111111111111111111')
+        assert_equal(result.account_sid, 'ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
+        assert_equal(result.domain_name, "test-trunk.pstn.twilio.com")
+        assert_equal(result.friendly_name, "Test")
+        assert_equal(result.recording,
+                     {"trim": "do-not-trim",
+                      "mode": "record-from-ringing"})
+
+        assert_equal(result.auth_type, "CREDENTIAL_LIST")
+        assert_equal(result.auth_type_set, ["CREDENTIAL_LIST"])
+        TRUNK_INSTANCE_BASE_URI = "{0}/{1}/{2}".format(BASE_URI, "Trunks",
+                                                       TRUNK_SID)
+
+        assert_equal(result.url, TRUNK_INSTANCE_BASE_URI)
+
+        assert_equal(result.links['origination_urls'],
+                     "{0}/OriginationUrls".format(TRUNK_INSTANCE_BASE_URI))
+        assert_equal(result.links['credential_lists'],
+                     "{0}/CredentialLists".format(TRUNK_INSTANCE_BASE_URI))
+        assert_equal(result.links['ip_access_control_lists'],
+                     "{0}/IpAccessControlLists".format(TRUNK_INSTANCE_BASE_URI))
+        assert_equal(result.links['phone_numbers'],
+                     "{0}/PhoneNumbers".format(TRUNK_INSTANCE_BASE_URI))
+
+        request.assert_called_with(
+            "GET",
+            "{0}/Trunks/TK11111111111111111111111111111111".format(BASE_URI),
+            auth=AUTH,
+            use_json_extension=False
+        )
+
+    @patch('twilio.rest.resources.base.make_twilio_request')
+    def test_create_trunk_instance(self, request):
+        resp = create_mock_json('tests/resources/trunking/trunks_instance.json')
+        resp.status_code = 201
+        request.return_value = resp
+
+        trunks = Trunks(BASE_URI, AUTH)
+        kwargs = {
+            'FriendlyName': 'Test',
+            'DomainName': 'test-trunk.pstn.twilio.com'
+        }
+        result = trunks.create(**kwargs)
+
+        assert_equal(result.sid, 'TK11111111111111111111111111111111')
+        assert_equal(result.account_sid, 'ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
+        assert_equal(result.domain_name, "test-trunk.pstn.twilio.com")
+        assert_equal(result.friendly_name, "Test")
+        assert_equal(result.recording,
+                     {"trim": "do-not-trim",
+                      "mode": "record-from-ringing"})
+
+        assert_equal(result.auth_type, "CREDENTIAL_LIST")
+        assert_equal(result.auth_type_set, ["CREDENTIAL_LIST"])
+        TRUNK_INSTANCE_BASE_URI = "{0}/{1}/{2}".format(BASE_URI, "Trunks",
+                                                       TRUNK_SID)
+
+        assert_equal(result.url, TRUNK_INSTANCE_BASE_URI)
+
+        assert_equal(result.links['origination_urls'],
+                     "{0}/OriginationUrls".format(TRUNK_INSTANCE_BASE_URI))
+        assert_equal(result.links['credential_lists'],
+                     "{0}/CredentialLists".format(TRUNK_INSTANCE_BASE_URI))
+        assert_equal(result.links['ip_access_control_lists'],
+                     "{0}/IpAccessControlLists".format(TRUNK_INSTANCE_BASE_URI))
+        assert_equal(result.links['phone_numbers'],
+                     "{0}/PhoneNumbers".format(TRUNK_INSTANCE_BASE_URI))
+
+        data_dict = dict()
+        data_dict['FriendlyName'] = 'Test'
+        data_dict['DomainName'] = 'test-trunk.pstn.twilio.com'
+
+        request.assert_called_with(
+            "POST",
+            "{0}/Trunks".format(BASE_URI),
+            auth=AUTH,
+            use_json_extension=False,
+            data=data_dict,
+        )
+
+    @patch('twilio.rest.resources.base.make_twilio_request')
+    def test_update_trunk_instance(self, request):
+        resp = create_mock_json('tests/resources/trunking/trunks_instance.json')
+        resp.status_code = 200
+        request.return_value = resp
+
+        trunks = Trunks(BASE_URI, AUTH)
+        result = trunks.update('TK11111111111111111111111111111111', {'FriendlyName': 'Test'})
+
+        assert_equal(result.sid, 'TK11111111111111111111111111111111')
+        assert_equal(result.account_sid, 'ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
+        assert_equal(result.domain_name, "test-trunk.pstn.twilio.com")
+        assert_equal(result.friendly_name, "Test")
+        assert_equal(result.recording,
+                     {"trim": "do-not-trim",
+                      "mode": "record-from-ringing"})
+
+        assert_equal(result.auth_type, "CREDENTIAL_LIST")
+        assert_equal(result.auth_type_set, ["CREDENTIAL_LIST"])
+        TRUNK_INSTANCE_BASE_URI = "{0}/{1}/{2}".format(BASE_URI, "Trunks",
+                                                       TRUNK_SID)
+
+        assert_equal(result.url, TRUNK_INSTANCE_BASE_URI)
+
+        assert_equal(result.links['origination_urls'],
+                     "{0}/OriginationUrls".format(TRUNK_INSTANCE_BASE_URI))
+        assert_equal(result.links['credential_lists'],
+                     "{0}/CredentialLists".format(TRUNK_INSTANCE_BASE_URI))
+        assert_equal(result.links['ip_access_control_lists'],
+                     "{0}/IpAccessControlLists".format(TRUNK_INSTANCE_BASE_URI))
+        assert_equal(result.links['phone_numbers'],
+                     "{0}/PhoneNumbers".format(TRUNK_INSTANCE_BASE_URI))
+
+        data_dict = dict()
+        data_dict['FriendlyName'] = 'Test'
+
+        request.assert_called_with(
+            "POST",
+            "{0}/Trunks/TK11111111111111111111111111111111".format(BASE_URI),
+            auth=AUTH,
+            use_json_extension=False,
+            data=data_dict
+        )
+
+    @patch('twilio.rest.resources.base.make_twilio_request')
+    def test_delete_trunk_instance(self, request):
+        resp = Mock()
+        resp.status_code = 204
+        request.return_value = resp
+
+        trunks = Trunks(BASE_URI, AUTH)
+        result = trunks.delete('TK11111111111111111111111111111111')
+
+        assert_true(result)
+
+        request.assert_called_with(
+            "DELETE",
+            "{0}/Trunks/TK11111111111111111111111111111111".format(BASE_URI),
+            auth=AUTH,
+            use_json_extension=False
+        )
diff --git a/tox.ini b/tox.ini
index 5249597112..51d8d59264 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = py26, py27, py32, py33, py34, pypy
+envlist = py26, py27, py32, py33, py34, py35, pypy
 
 [testenv]
 deps= -r{toxinidir}/tests/requirements.txt
diff --git a/twilio/access_token.py b/twilio/access_token.py
new file mode 100644
index 0000000000..9621c041ad
--- /dev/null
+++ b/twilio/access_token.py
@@ -0,0 +1,150 @@
+import time
+from twilio import jwt
+
+
+class IpMessagingGrant(object):
+    """ Grant to access Twilio IP Messaging """
+    def __init__(self, service_sid=None, endpoint_id=None,
+                 deployment_role_sid=None, push_credential_sid=None):
+        self.service_sid = service_sid
+        self.endpoint_id = endpoint_id
+        self.deployment_role_sid = deployment_role_sid
+        self.push_credential_sid = push_credential_sid
+
+    @property
+    def key(self):
+        return "ip_messaging"
+
+    def to_payload(self):
+        grant = {}
+        if self.service_sid:
+            grant['service_sid'] = self.service_sid
+        if self.endpoint_id:
+            grant['endpoint_id'] = self.endpoint_id
+        if self.deployment_role_sid:
+            grant['deployment_role_sid'] = self.deployment_role_sid
+        if self.push_credential_sid:
+            grant['push_credential_sid'] = self.push_credential_sid
+
+        return grant
+
+
+class ConversationsGrant(object):
+    """ Grant to access Twilio Conversations """
+    def __init__(self, configuration_profile_sid=None):
+        self.configuration_profile_sid = configuration_profile_sid
+
+    @property
+    def key(self):
+        return "rtc"
+
+    def to_payload(self):
+        grant = {}
+        if self.configuration_profile_sid:
+            grant['configuration_profile_sid'] = self.configuration_profile_sid
+
+        return grant
+
+
+class VoiceGrant(object):
+    """ Grant to access Twilio Programmable Voice"""
+    def __init__(self,
+                 outgoing_application_sid=None,
+                 outgoing_application_params=None,
+                 push_credential_sid=None,
+                 endpoint_id=None):
+        self.outgoing_application_sid = outgoing_application_sid
+        """ :type : str """
+        self.outgoing_application_params = outgoing_application_params
+        """ :type : dict """
+        self.push_credential_sid = push_credential_sid
+        """ :type : str """
+        self.endpoint_id = endpoint_id
+        """ :type : str """
+
+    @property
+    def key(self):
+        return "voice"
+
+    def to_payload(self):
+        grant = {}
+        if self.outgoing_application_sid:
+            grant['outgoing'] = {}
+            grant['outgoing']['application_sid'] = \
+                self.outgoing_application_sid
+
+            if self.outgoing_application_params:
+                grant['outgoing']['params'] = self.outgoing_application_params
+
+        if self.push_credential_sid:
+            grant['push_credential_sid'] = self.push_credential_sid
+
+        if self.endpoint_id:
+            grant['endpoint_id'] = self.endpoint_id
+
+        return grant
+
+
+class VideoGrant(object):
+    """ Grant to access Twilio Video """
+    def __init__(self, configuration_profile_sid=None):
+        self.configuration_profile_sid = configuration_profile_sid
+
+    @property
+    def key(self):
+        return "video"
+
+    def to_payload(self):
+        grant = {}
+        if self.configuration_profile_sid:
+            grant['configuration_profile_sid'] = self.configuration_profile_sid
+
+        return grant
+
+
+class AccessToken(object):
+    """ Access Token used to access Twilio Resources """
+    def __init__(self, account_sid, signing_key_sid, secret,
+                 identity=None, ttl=3600, nbf=None):
+        self.account_sid = account_sid
+        self.signing_key_sid = signing_key_sid
+        self.secret = secret
+
+        self.identity = identity
+        self.ttl = ttl
+        self.nbf = nbf
+        self.grants = []
+
+    def add_grant(self, grant):
+        self.grants.append(grant)
+
+    def to_jwt(self, algorithm='HS256'):
+        now = int(time.time())
+        headers = {
+            "typ": "JWT",
+            "cty": "twilio-fpa;v=1"
+        }
+
+        grants = {}
+        if self.identity:
+            grants["identity"] = self.identity
+
+        for grant in self.grants:
+            grants[grant.key] = grant.to_payload()
+
+        payload = {
+            "jti": '{0}-{1}'.format(self.signing_key_sid, now),
+            "iss": self.signing_key_sid,
+            "sub": self.account_sid,
+            "exp": now + self.ttl,
+            "grants": grants
+        }
+
+        if self.nbf is not None:
+            payload['nbf'] = self.nbf
+
+        return jwt.encode(payload, self.secret, headers=headers,
+                          algorithm=algorithm)
+
+    def __str__(self):
+        return self.to_jwt()
diff --git a/twilio/jwt/__init__.py b/twilio/jwt/__init__.py
index edb4062433..93f6b60a34 100644
--- a/twilio/jwt/__init__.py
+++ b/twilio/jwt/__init__.py
@@ -41,9 +41,11 @@ def base64url_encode(input):
     return base64.urlsafe_b64encode(input).decode('utf-8').replace('=', '')
 
 
-def encode(payload, key, algorithm='HS256'):
+def encode(payload, key, algorithm='HS256', headers=None):
     segments = []
     header = {"typ": "JWT", "alg": algorithm}
+    if headers:
+        header.update(headers)
     segments.append(base64url_encode(binary(json.dumps(header))))
     segments.append(base64url_encode(binary(json.dumps(payload))))
     sign_input = '.'.join(segments)
diff --git a/twilio/rest/__init__.py b/twilio/rest/__init__.py
index 31c6338fea..f4077334aa 100644
--- a/twilio/rest/__init__.py
+++ b/twilio/rest/__init__.py
@@ -1,8 +1,11 @@
 from .base import set_twilio_proxy
 from .client import TwilioRestClient
+from .ip_messaging import TwilioIpMessagingClient
 from .lookups import TwilioLookupsClient
 from .pricing import TwilioPricingClient
 from .task_router import TwilioTaskRouterClient
+from .trunking import TwilioTrunkingClient
 
-_hush_pyflakes = [set_twilio_proxy, TwilioRestClient, TwilioLookupsClient,
-                  TwilioPricingClient, TwilioTaskRouterClient]
+_hush_pyflakes = [set_twilio_proxy, TwilioRestClient, TwilioIpMessagingClient,
+                  TwilioLookupsClient, TwilioPricingClient,
+                  TwilioTaskRouterClient, TwilioTrunkingClient]
diff --git a/twilio/rest/base.py b/twilio/rest/base.py
index 050321c59c..7d7a2d9702 100644
--- a/twilio/rest/base.py
+++ b/twilio/rest/base.py
@@ -30,8 +30,8 @@ def set_twilio_proxy(proxy_url, proxy_port):
 
 class TwilioClient(object):
     def __init__(self, account=None, token=None, base="https://api.twilio.com",
-                 version="2010-04-01",
-                 timeout=UNSET_TIMEOUT):
+                 version="2010-04-01", timeout=UNSET_TIMEOUT,
+                 request_account=None):
         """
         Create a Twilio API client.
         """
@@ -58,8 +58,9 @@ def __init__(self, account=None, token=None, base="https://api.twilio.com",
         self.base = base
         self.auth = (account, token)
         self.timeout = timeout
+        req_account = request_account if request_account else account
         self.account_uri = "{0}/{1}/Accounts/{2}".format(base,
-                                                         version, account)
+                                                         version, req_account)
 
     def request(self, path, method=None, vars=None):
         """sends a request and gets a response from the Twilio REST API
diff --git a/twilio/rest/client.py b/twilio/rest/client.py
index 9537cf41bf..ed89641e30 100644
--- a/twilio/rest/client.py
+++ b/twilio/rest/client.py
@@ -2,6 +2,7 @@
 from twilio.rest.resources import (
     UNSET_TIMEOUT,
     Accounts,
+    Addresses,
     Applications,
     AuthorizedConnectApps,
     CallFeedback,
@@ -11,6 +12,7 @@
     Conferences,
     ConnectApps,
     DependentPhoneNumbers,
+    Keys,
     MediaList,
     Members,
     Messages,
@@ -40,12 +42,13 @@ class TwilioRestClient(TwilioClient):
     """
 
     def __init__(self, account=None, token=None, base="https://api.twilio.com",
-                 version="2010-04-01", timeout=UNSET_TIMEOUT):
+                 version="2010-04-01", timeout=UNSET_TIMEOUT,
+                 request_account=None):
         """
         Create a Twilio REST API client.
         """
         super(TwilioRestClient, self).__init__(account, token, base, version,
-                                               timeout)
+                                               timeout, request_account)
 
         version_uri = "%s/%s" % (base, version)
 
@@ -56,6 +59,7 @@ def __init__(self, account=None, token=None, base="https://api.twilio.com",
             self.auth,
             timeout
         )
+        self.addresses = Addresses(self.account_uri, self.auth, timeout)
         self.calls = Calls(self.account_uri, self.auth, timeout)
         self.caller_ids = CallerIds(self.account_uri, self.auth, timeout)
         self.connect_apps = ConnectApps(self.account_uri, self.auth, timeout)
@@ -74,6 +78,7 @@ def __init__(self, account=None, token=None, base="https://api.twilio.com",
         self.media = MediaList(self.account_uri, self.auth, timeout)
         self.sip = Sip(self.account_uri, self.auth, timeout)
         self.tokens = Tokens(self.account_uri, self.auth, timeout)
+        self.keys = Keys(self.account_uri, self.auth, timeout)
 
     def participants(self, conference_sid):
         """
diff --git a/twilio/rest/ip_messaging.py b/twilio/rest/ip_messaging.py
new file mode 100644
index 0000000000..033864c801
--- /dev/null
+++ b/twilio/rest/ip_messaging.py
@@ -0,0 +1,32 @@
+from twilio.rest.base import TwilioClient
+from twilio.rest.resources import UNSET_TIMEOUT
+from twilio.rest.resources.ip_messaging.services import Services
+from twilio.rest.resources.ip_messaging.credentials import Credentials
+
+
+class TwilioIpMessagingClient(TwilioClient):
+    """
+    A client for accessing the Twilio IP Messaging API.
+
+    The Twilio IP Messaging API provides information about events. For more
+    information, see the
+    `IP Messaging API documentation <https://www.twilio.com/docs/XXX>`_.
+
+    :param str account: Your Account Sid from `your dashboard
+        <https://www.twilio.com/user/account>`_
+    :param str token: Your Auth Token from `your dashboard
+        <https://www.twilio.com/user/account>`_
+    :param float timeout: The socket and read timeout for requests to Twilio
+    """
+
+    def __init__(self, account=None, token=None,
+                 base="https://ip-messaging.twilio.com", version="v1",
+                 timeout=UNSET_TIMEOUT, request_account=None):
+
+        super(TwilioIpMessagingClient, self).__init__(account, token, base,
+                                                      version, timeout,
+                                                      request_account)
+
+        self.version_uri = "%s/%s" % (base, version)
+        self.services = Services(self.version_uri, self.auth, timeout)
+        self.credentials = Credentials(self.version_uri, self.auth, timeout)
diff --git a/twilio/rest/lookups.py b/twilio/rest/lookups.py
index b71e30da55..8d591825c3 100644
--- a/twilio/rest/lookups.py
+++ b/twilio/rest/lookups.py
@@ -20,10 +20,11 @@ class TwilioLookupsClient(TwilioClient):
 
     def __init__(self, account=None, token=None,
                  base="https://lookups.twilio.com", version="v1",
-                 timeout=UNSET_TIMEOUT):
+                 timeout=UNSET_TIMEOUT, request_account=None):
 
         super(TwilioLookupsClient, self).__init__(account, token, base,
-                                                  version, timeout)
+                                                  version, timeout,
+                                                  request_account)
 
         self.version_uri = "%s/%s" % (base, version)
         self.phone_numbers = PhoneNumbers(self.version_uri, self.auth, timeout)
diff --git a/twilio/rest/monitor.py b/twilio/rest/monitor.py
index 980e2f3c0b..9554a844ff 100644
--- a/twilio/rest/monitor.py
+++ b/twilio/rest/monitor.py
@@ -21,10 +21,11 @@ class TwilioMonitorClient(TwilioClient):
 
     def __init__(self, account=None, token=None,
                  base="https://monitor.twilio.com", version="v1",
-                 timeout=UNSET_TIMEOUT):
+                 timeout=UNSET_TIMEOUT, request_account=None):
 
         super(TwilioMonitorClient, self).__init__(account, token, base,
-                                                  version, timeout)
+                                                  version, timeout,
+                                                  request_account)
 
         self.version_uri = "%s/%s" % (base, version)
         self.events = Events(self.version_uri, self.auth, timeout)
diff --git a/twilio/rest/pricing.py b/twilio/rest/pricing.py
index 998c711ac1..61e7a84058 100644
--- a/twilio/rest/pricing.py
+++ b/twilio/rest/pricing.py
@@ -3,6 +3,7 @@
 from twilio.rest.resources.pricing import (
     PhoneNumbers,
     Voice,
+    MessagingCountries,
 )
 
 
@@ -20,11 +21,23 @@ class TwilioPricingClient(TwilioClient):
 
     def __init__(self, account=None, token=None,
                  base="https://pricing.twilio.com", version="v1",
-                 timeout=UNSET_TIMEOUT):
+                 timeout=UNSET_TIMEOUT, request_account=None):
         super(TwilioPricingClient, self).__init__(account, token, base,
-                                                  version, timeout)
+                                                  version, timeout,
+                                                  request_account)
 
-        uri_base = "{}/{}".format(base, version)
+        self.uri_base = "{}/{}".format(base, version)
 
-        self.voice = Voice(uri_base, self.auth, self.timeout)
-        self.phone_numbers = PhoneNumbers(uri_base, self.auth, self.timeout)
+        self.voice = Voice(self.uri_base, self.auth, self.timeout)
+        self.phone_numbers = PhoneNumbers(self.uri_base, self.auth,
+                                          self.timeout)
+
+    def messaging_countries(self):
+        """
+        Returns a :class:`MessagingCountries` resource
+        :return: MessagingCountries
+        """
+        messaging_countries_uri = "{0}/Messaging".format(
+            self.uri_base)
+        return MessagingCountries(messaging_countries_uri, self.auth,
+                                  self.timeout)
diff --git a/twilio/rest/resources/__init__.py b/twilio/rest/resources/__init__.py
index fa9aef58ec..7e74cad645 100644
--- a/twilio/rest/resources/__init__.py
+++ b/twilio/rest/resources/__init__.py
@@ -73,3 +73,16 @@
     DependentPhoneNumber,
     DependentPhoneNumbers,
 )
+
+from .trunking import (
+    CredentialList,
+    CredentialLists,
+    IpAccessControlList,
+    IpAccessControlLists,
+    OriginationUrl,
+    OriginationUrls,
+    Trunk,
+    Trunks,
+)
+
+from .keys import Key, Keys
diff --git a/twilio/rest/resources/accounts.py b/twilio/rest/resources/accounts.py
index d54b9d4067..78c2f65370 100644
--- a/twilio/rest/resources/accounts.py
+++ b/twilio/rest/resources/accounts.py
@@ -9,6 +9,7 @@
 from .conferences import Conferences
 from .connect_apps import ConnectApps, AuthorizedConnectApps
 from .queues import Queues
+from .keys import Keys
 from .usage import UsageRecords, UsageTriggers
 from .messages import Messages
 from .media import MediaList
@@ -42,6 +43,7 @@ class Account(InstanceResource):
         MediaList,
         Messages,
         Sip,
+        Keys,
     ]
 
     def update(self, **kwargs):
diff --git a/twilio/rest/resources/ip_messaging/__init__.py b/twilio/rest/resources/ip_messaging/__init__.py
new file mode 100644
index 0000000000..55f60ad203
--- /dev/null
+++ b/twilio/rest/resources/ip_messaging/__init__.py
@@ -0,0 +1,34 @@
+from .services import (
+    Service,
+    Services
+)
+
+from .channels import (
+    Channel,
+    Channels
+)
+
+from .members import (
+    Member,
+    Members
+)
+
+from .messages import (
+    Message,
+    Messages
+)
+
+from .roles import (
+    Role,
+    Roles
+)
+
+from .users import (
+    User,
+    Users
+)
+
+from .credentials import (
+    Credential,
+    Credentials
+)
diff --git a/twilio/rest/resources/ip_messaging/channels.py b/twilio/rest/resources/ip_messaging/channels.py
new file mode 100644
index 0000000000..b7e4f5529a
--- /dev/null
+++ b/twilio/rest/resources/ip_messaging/channels.py
@@ -0,0 +1,78 @@
+from .members import Members
+from .messages import Messages
+from twilio.rest.resources import NextGenInstanceResource, NextGenListResource
+
+
+class Channel(NextGenInstanceResource):
+
+    subresources = [
+        Members,
+        Messages
+    ]
+
+    def update(self, **kwargs):
+        """
+        Updates the Channel instance
+        :param sid: Channel instance identifier
+        :param type: Channel type
+        :param friendly_name: Channel's friendly name
+        :param unique_name: Channel's Unique name
+        :param attributes: Additional attributes that needs to be stored with
+        channel
+        :return: the updated instance
+        """
+        return self.update_instance(**kwargs)
+
+    def delete(self):
+        """
+        Delete this channel
+        """
+        return self.delete_instance()
+
+
+class Channels(NextGenListResource):
+
+    name = "Channels"
+    instance = Channel
+
+    def list(self, **kwargs):
+        """
+        Returns a page of :class:`Channel` resources as a list.
+        For paging information see :class:`ListResource`.
+
+        **NOTE**: Due to the potentially voluminous amount of data in an
+        alert, the full HTTP request and response data is only returned
+        in the Channel instance resource representation.
+        """
+        return self.get_instances(kwargs)
+
+    def create(self, **kwargs):
+        """
+        Create a channel.
+
+        :param str friendly_name: Channel's friendly name
+        :param unique_name: Channel's Unique name
+        :param str attributes: Developer-specific data (json) storage
+
+        :return: A :class:`Channel` object
+        """
+        return self.create_instance(kwargs)
+
+    def delete(self, sid):
+        """
+        Delete a given Channel
+        """
+        return self.delete_instance(sid)
+
+    def update(self, sid, **kwargs):
+        """
+        Updates the Channel instance identified by sid
+        :param sid: Channel instance identifier
+        :param type: Channel type
+        :param friendly_name: Channel's friendly name
+        :param unique_name: Channel's Unique name
+        :param attributes: Additional attributes that needs to be stored with
+        channel
+        :return: Updated instance
+        """
+        return self.update_instance(sid, kwargs)
diff --git a/twilio/rest/resources/ip_messaging/credentials.py b/twilio/rest/resources/ip_messaging/credentials.py
new file mode 100644
index 0000000000..3b84f85364
--- /dev/null
+++ b/twilio/rest/resources/ip_messaging/credentials.py
@@ -0,0 +1,80 @@
+from twilio.rest.resources import NextGenInstanceResource, NextGenListResource
+
+
+class Credential(NextGenInstanceResource):
+
+    def update(self, credential_type, **kwargs):
+        """
+        Updates this Credential instance
+        :param sid: Credential instance identifier
+        :param credential_type: Credential type
+        :param friendly_name: Credential's friendly name
+        :param certificate: Credential's certificate
+        :param private_key: Credential's Private key
+        :param sandbox: Credential's Sandbox
+        :param api_key: Credential's Api Key
+        :return: Updated instance
+        """
+        kwargs['type'] = credential_type
+        return self.update_instance(**kwargs)
+
+    def delete(self):
+        """
+        Delete this credential
+        """
+        return self.delete_instance()
+
+
+class Credentials(NextGenListResource):
+
+    name = "Credentials"
+    instance = Credential
+
+    def list(self, **kwargs):
+        """
+        Returns a page of :class:`Credential` resources as a list.
+        For paging information see :class:`ListResource`.
+
+        **NOTE**: Due to the potentially voluminous amount of data in an
+        alert, the full HTTP request and response data is only returned
+        in the Credential instance resource representation.
+
+        :param date after: Only list alerts logged after this datetime
+        :param date before: Only list alerts logger before this datetime
+        :param log_level: If 'error', only shows errors. If 'warning',
+         only show warnings
+        """
+        return self.get_instances(kwargs)
+
+    def create(self, credential_type, **kwargs):
+        """
+        Make a phone call to a number.
+
+        :param str credential_type: The type of credential
+        :param str friendly_name: The friendly name of the credential.
+
+        :return: A :class:`Credential` object
+        """
+        kwargs["type"] = credential_type
+        return self.create_instance(kwargs)
+
+    def delete(self, sid):
+        """
+        Delete a given Credential
+        """
+        return self.delete_instance(sid)
+
+    def update(self, sid, credential_type, **kwargs):
+        """
+        Updates the Credential instance identified by sid
+        :param sid: Credential instance identifier
+        :param credential_type: Credential type
+        :param friendly_name: Credential's friendly name
+        :param certificate: Credential's certificate
+        :param private_key: Credential's Private key
+        :param sandbox: Credential's Sandbox
+        :param api_key: Credential's Api Key
+        :return: Updated instance
+        """
+        kwargs['type'] = credential_type
+        return self.update_instance(sid, kwargs)
diff --git a/twilio/rest/resources/ip_messaging/members.py b/twilio/rest/resources/ip_messaging/members.py
new file mode 100644
index 0000000000..dfdaadf149
--- /dev/null
+++ b/twilio/rest/resources/ip_messaging/members.py
@@ -0,0 +1,68 @@
+from twilio.rest.resources import NextGenInstanceResource, NextGenListResource
+
+
+class Member(NextGenInstanceResource):
+
+    def update(self, role_sid, **kwargs):
+        """
+        Updates the Member instance identified by sid
+        :param sid: Member instance identifier
+        :param role_sid: Member's Role Sid
+        :param identity: Member's Identity
+        :return: Updated instance
+        """
+        kwargs['role_sid'] = role_sid
+        return self.update_instance(**kwargs)
+
+    def delete(self):
+        """
+        Delete this member
+        """
+        return self.delete_instance()
+
+
+class Members(NextGenListResource):
+
+    name = "Members"
+    instance = Member
+
+    def list(self, **kwargs):
+        """
+        Returns a page of :class:`Member` resources as a list.
+        For paging information see :class:`ListResource`.
+
+        **NOTE**: Due to the potentially voluminous amount of data in an
+        alert, the full HTTP request and response data is only returned
+        in the Member instance resource representation.
+
+        """
+        return self.get_instances(kwargs)
+
+    def create(self, identity, **kwargs):
+        """
+        Create a Member.
+
+        :param str identity: The identity of the user.
+        :param str role: The role to assign the member.
+
+        :return: A :class:`Member` object
+        """
+        kwargs["identity"] = identity
+        return self.create_instance(kwargs)
+
+    def delete(self, sid):
+        """
+        Delete a given Member
+        """
+        return self.delete_instance(sid)
+
+    def update(self, sid, role_sid, **kwargs):
+        """
+        Updates the Member instance identified by sid
+        :param sid: Member instance identifier
+        :param role_sid: Member's Role Sid
+        :param identity: Member's Identity
+        :return: Updated instance
+        """
+        kwargs['role_sid'] = role_sid
+        return self.update_instance(sid, kwargs)
diff --git a/twilio/rest/resources/ip_messaging/messages.py b/twilio/rest/resources/ip_messaging/messages.py
new file mode 100644
index 0000000000..848ba16095
--- /dev/null
+++ b/twilio/rest/resources/ip_messaging/messages.py
@@ -0,0 +1,68 @@
+from twilio.rest.resources import NextGenInstanceResource, NextGenListResource
+
+
+class Message(NextGenInstanceResource):
+
+    def update(self, **kwargs):
+        """
+        Updates the Message instance
+        :param sid: Message instance identifier
+        :param service_sid: Service instance identifier
+        :param channel_sid: Channel instance identifier
+        :param body: Message's body
+        :return: the updated instance
+        """
+        return self.update_instance(**kwargs)
+
+    def delete(self):
+        """
+        Delete this message
+        """
+        return self.delete_instance()
+
+
+class Messages(NextGenListResource):
+
+    name = "Messages"
+    instance = Message
+
+    def list(self, **kwargs):
+        """
+        Returns a page of :class:`Message` resources as a list.
+        For paging information see :class:`ListResource`.
+
+        **NOTE**: Due to the potentially voluminous amount of data in an
+        alert, the full HTTP request and response data is only returned
+        in the Message instance resource representation.
+
+        """
+        return self.get_instances(kwargs)
+
+    def create(self, body, **kwargs):
+        """
+        Create a Message.
+
+        :param str body: The body of the message.
+        :param str from: The message author's identity.
+
+        :return: A :class:`Message` object
+        """
+        kwargs["body"] = body
+        return self.create_instance(kwargs)
+
+    def delete(self, sid):
+        """
+        Delete a given Message
+        """
+        return self.delete_instance(sid)
+
+    def update(self, sid, **kwargs):
+        """
+        Updates the Message instance identified by sid
+        :param sid: Message instance identifier
+        :param service_sid: Service instance identifier
+        :param channel_sid: Channel instance identifier
+        :param body: Message's body
+        :return: the updated instance
+        """
+        return self.update_instance(sid, kwargs)
diff --git a/twilio/rest/resources/ip_messaging/roles.py b/twilio/rest/resources/ip_messaging/roles.py
new file mode 100644
index 0000000000..3e1f232842
--- /dev/null
+++ b/twilio/rest/resources/ip_messaging/roles.py
@@ -0,0 +1,67 @@
+from twilio.rest.resources import NextGenInstanceResource, NextGenListResource
+
+
+class Role(NextGenInstanceResource):
+
+    def update(self, permission, **kwargs):
+        """
+        Updates this Role instance
+        :param permission: Role permission
+        :return: Updated instance
+        """
+        kwargs['permission'] = permission
+        return self.update_instance(**kwargs)
+
+    def delete(self):
+        """
+        Delete this role
+        """
+        return self.delete_instance()
+
+
+class Roles(NextGenListResource):
+
+    name = "Roles"
+    instance = Role
+
+    def list(self, **kwargs):
+        """
+        Returns a page of :class:`Role` resources as a list.
+        For paging information see :class:`ListResource`.
+
+        **NOTE**: Due to the potentially voluminous amount of data in an
+        alert, the full HTTP request and response data is only returned
+        in the Role instance resource representation.
+
+        """
+        return self.get_instances(kwargs)
+
+    def delete(self, sid):
+        """
+        Delete a given Role
+        """
+        return self.delete_instance(sid)
+
+    def create(self, friendly_name, role_type, permission):
+        """
+        Creates a Role
+        :param str friendly_name: Human readable name to the Role
+        :param str role_type: Type of role - deployment or channel
+        :param str permission: Set of permissions for the role
+        """
+        kwargs = {
+            "friendly_name": friendly_name,
+            "type": role_type,
+            "permission": permission
+        }
+        return self.create_instance(kwargs)
+
+    def update(self, sid, permission, **kwargs):
+        """
+        Updates the Role instance identified by sid
+        :param sid: Role instance identifier
+        :param permission: Role permission
+        :return: Updated instance
+        """
+        kwargs['permission'] = permission
+        return self.update_instance(sid, kwargs)
diff --git a/twilio/rest/resources/ip_messaging/services.py b/twilio/rest/resources/ip_messaging/services.py
new file mode 100644
index 0000000000..f22c30260a
--- /dev/null
+++ b/twilio/rest/resources/ip_messaging/services.py
@@ -0,0 +1,69 @@
+from .channels import Channels
+from .roles import Roles
+from .users import Users
+from twilio.rest.resources import NextGenInstanceResource, NextGenListResource
+
+
+class Service(NextGenInstanceResource):
+
+    subresources = [
+        Channels,
+        Roles,
+        Users
+    ]
+
+    def update(self, **kwargs):
+        """
+        Updates this Service instance
+        :return: Updated instance
+        """
+        return self.update_instance(**kwargs)
+
+    def delete(self):
+        """
+        Delete this service
+        """
+        return self.delete_instance()
+
+
+class Services(NextGenListResource):
+
+    name = "Services"
+    instance = Service
+
+    def list(self, **kwargs):
+        """
+        Returns a page of :class:`Service` resources as a list.
+        For paging information see :class:`ListResource`.
+
+        **NOTE**: Due to the potentially voluminous amount of data in an
+        alert, the full HTTP request and response data is only returned
+        in the Service instance resource representation.
+
+        """
+        return self.get_instances(kwargs)
+
+    def create(self, friendly_name, **kwargs):
+        """
+        Create a service.
+
+        :param str friendly_name: The friendly name for the service
+
+        :return: A :class:`Service` object
+        """
+        kwargs["friendly_name"] = friendly_name
+        return self.create_instance(kwargs)
+
+    def delete(self, sid):
+        """
+        Delete a given Service
+        """
+        return self.delete_instance(sid)
+
+    def update(self, sid, **kwargs):
+        """
+        Updates the Service instance identified by sid
+        :param sid: Service instance identifier
+        :return: Updated instance
+        """
+        return self.update_instance(sid, kwargs)
diff --git a/twilio/rest/resources/ip_messaging/users.py b/twilio/rest/resources/ip_messaging/users.py
new file mode 100644
index 0000000000..1439772cc0
--- /dev/null
+++ b/twilio/rest/resources/ip_messaging/users.py
@@ -0,0 +1,63 @@
+from twilio.rest.resources import NextGenInstanceResource, NextGenListResource
+
+
+class User(NextGenInstanceResource):
+
+    def update(self, **kwargs):
+        """
+        Updates this User instance
+        :param role_sid: The role to assign the user.
+        :return: Updated instance
+        """
+        return self.update_instance(**kwargs)
+
+    def delete(self):
+        """
+        Delete this user
+        """
+        return self.delete_instance()
+
+
+class Users(NextGenListResource):
+
+    name = "Users"
+    instance = User
+
+    def list(self, **kwargs):
+        """
+        Returns a page of :class:`User` resources as a list.
+        For paging information see :class:`ListResource`.
+
+        **NOTE**: Due to the potentially voluminous amount of data in an
+        alert, the full HTTP request and response data is only returned
+        in the User instance resource representation.
+
+        """
+        return self.get_instances(kwargs)
+
+    def create(self, identity, **kwargs):
+        """
+        Creates a User
+
+        :param str identity: The identity of the user.
+        :param str role_sid: The role to assign the user.
+
+        :return: A :class:`User` object
+        """
+        kwargs["identity"] = identity
+        return self.create_instance(kwargs)
+
+    def delete(self, sid):
+        """
+        Delete a given User
+        """
+        return self.delete_instance(sid)
+
+    def update(self, sid, **kwargs):
+        """
+        Updates the User instance identified by sid
+        :param sid: User instance identifier
+        :param role_sid: The role to assign the user.
+        :return: Updated instance
+        """
+        return self.update_instance(sid, kwargs)
diff --git a/twilio/rest/resources/keys.py b/twilio/rest/resources/keys.py
new file mode 100644
index 0000000000..f68698232c
--- /dev/null
+++ b/twilio/rest/resources/keys.py
@@ -0,0 +1,75 @@
+from twilio.rest.resources.base import InstanceResource, ListResource
+
+
+class Key(InstanceResource):
+    """
+    A key resource.
+    See https://www.twilio.com/docs/api/rest-keys
+
+    .. attribute:: sid
+
+        The unique ID for this key.
+
+    .. attribute:: friendly_name
+
+        A human-readable description of this key.
+
+    .. attribute:: secret
+
+        This key's secret.
+
+    .. attribute:: date_created
+
+        The date this key was created, given as UTC in ISO 8601 format.
+
+    .. attribute:: date_updated
+
+        The date this singing key was last updated, given as UTC in ISO 8601
+        format.
+    """
+
+    def update(self, **kwargs):
+        """
+        Update this key
+        """
+        return self.parent.update(self.name, **kwargs)
+
+    def delete(self):
+        """
+        Delete this key
+        """
+        return self.parent.delete(self.name)
+
+
+class Keys(ListResource):
+    name = "Keys"
+    key = "keys"
+    instance = Key
+
+    def create(self, **kwargs):
+        """
+        Create a :class:`Key` with any of these optional parameters.
+
+        :param friendly_name: A human readable description of the signing key.
+        """
+        return self.create_instance(kwargs)
+
+    def update(self, sid, **kwargs):
+        """
+        Update a :class:`Key` with the given parameters.
+
+        All the parameters are describe above in :meth:`create`
+        """
+        return self.update_instance(sid, kwargs)
+
+    def delete(self, sid):
+        """
+        Delete a :class:`Key`
+        """
+        return self.delete_instance(sid)
+
+    def list(self, **kwargs):
+        """
+        Returns a page of :class:`Key` resources as a list
+        """
+        return self.get_instances(kwargs)
diff --git a/twilio/rest/resources/messages.py b/twilio/rest/resources/messages.py
index 90779b1bc2..a554975268 100644
--- a/twilio/rest/resources/messages.py
+++ b/twilio/rest/resources/messages.py
@@ -116,7 +116,7 @@ def create(self, from_=None, **kwargs):
         :param status_callback: A URL that Twilio will POST to when
             your message is processed.
         :param str application_sid: The 34 character sid of the application
-            Twilio should use to handle this phone call.
+            Twilio should use to handle this message.
         """
         kwargs["from"] = from_
         return self.create_instance(kwargs)
diff --git a/twilio/rest/resources/phone_numbers.py b/twilio/rest/resources/phone_numbers.py
index 91b0042210..16557d3c21 100644
--- a/twilio/rest/resources/phone_numbers.py
+++ b/twilio/rest/resources/phone_numbers.py
@@ -325,7 +325,8 @@ def search(self, **kwargs):
         :param str region: When searching the US, show numbers in this state
         :param str postal_code: Only show numbers in this area code
         :param str rate_center: US only.
-        :param tuple near_lat_long: Find close numbers within Distance miles.
+        :param str near_lat_long: Find close numbers within Distance miles.
+            Should be string of format "{lat},{long}"
         :param integer distance: Search radius for a Near- query in miles.
         :param boolean beta: Whether to include numbers new to the Twilio
             platform.
diff --git a/twilio/rest/resources/pricing/__init__.py b/twilio/rest/resources/pricing/__init__.py
index b1797a0a87..1edd5d260b 100644
--- a/twilio/rest/resources/pricing/__init__.py
+++ b/twilio/rest/resources/pricing/__init__.py
@@ -11,3 +11,7 @@
     PhoneNumberCountry,
     PhoneNumbers,
 )
+
+from .messaging_countries import (
+    MessagingCountries
+)
diff --git a/twilio/rest/resources/pricing/messaging_countries.py b/twilio/rest/resources/pricing/messaging_countries.py
new file mode 100644
index 0000000000..2be1a6e097
--- /dev/null
+++ b/twilio/rest/resources/pricing/messaging_countries.py
@@ -0,0 +1,39 @@
+from .. import NextGenInstanceResource, NextGenListResource
+
+
+class MessagingCountry(NextGenInstanceResource):
+    """Pricing information for Twilio Messages in a specific country.
+
+    .. attribute:: iso_country
+
+        The country's 2-character ISO 3166-1 code.
+
+    """
+    id_key = "iso_country"
+
+
+class MessagingCountries(NextGenListResource):
+    """A list of countries where Twilio Messages are available.
+
+    The returned list of MessagingCountry objects will not have pricing
+    information populated. To get pricing information for a specific country,
+    retrieve it with the :meth:`get` method.
+    """
+
+    instance = MessagingCountry
+    key = "countries"
+    name = "Countries"
+
+    def get(self, iso_country):
+        """Retrieve pricing information for Twilio Messages in the specified
+        country.
+
+        :param iso_country: The two-letter ISO code for the country
+        """
+        return self.get_instance(iso_country)
+
+    def list(self, **kwargs):
+        """Retrieve the list of countries in which Twilio Messages are
+        available."""
+
+        return super(MessagingCountries, self).list(**kwargs)
diff --git a/twilio/rest/resources/recordings.py b/twilio/rest/resources/recordings.py
index 6a4dc227da..be88909144 100644
--- a/twilio/rest/resources/recordings.py
+++ b/twilio/rest/resources/recordings.py
@@ -41,6 +41,18 @@ def list(self, before=None, after=None, **kwargs):
         kwargs["DateCreated>"] = after
         return self.get_instances(kwargs)
 
+    @normalize_dates
+    def iter(self, before=None, after=None, **kwargs):
+        """
+        Returns an iterator of :class:`Recording` resources.
+
+        :param date after: Only list recordings logged after this datetime
+        :param date before: Only list recordings logger before this datetime
+        """
+        kwargs["DateCreated<"] = before
+        kwargs["DateCreated>"] = after
+        return super(Recordings, self).iter(**kwargs)
+
     def delete(self, sid):
         """
         Delete the given recording
diff --git a/twilio/rest/resources/task_router/workers.py b/twilio/rest/resources/task_router/workers.py
index f257435cb9..6383a57d0c 100644
--- a/twilio/rest/resources/task_router/workers.py
+++ b/twilio/rest/resources/task_router/workers.py
@@ -1,5 +1,6 @@
 from .. import NextGenInstanceResource, NextGenListResource
 from .statistics import Statistics
+from .reservations import Reservations
 
 
 class Worker(NextGenInstanceResource):
@@ -68,7 +69,8 @@ class Worker(NextGenInstanceResource):
         calculate :class: `Workflow` statistics.
     """
     subresources = [
-        Statistics
+        Statistics,
+        Reservations
     ]
 
     def delete(self):
diff --git a/twilio/rest/resources/trunking/__init__.py b/twilio/rest/resources/trunking/__init__.py
new file mode 100644
index 0000000000..887e5d9d3f
--- /dev/null
+++ b/twilio/rest/resources/trunking/__init__.py
@@ -0,0 +1,24 @@
+from .credential_lists import (
+    CredentialList,
+    CredentialLists
+)
+
+from .ip_access_control_lists import (
+    IpAccessControlList,
+    IpAccessControlLists
+)
+
+from .origination_urls import (
+    OriginationUrl,
+    OriginationUrls
+)
+
+from .phone_numbers import (
+    PhoneNumber,
+    PhoneNumbers
+)
+
+from .trunks import (
+    Trunk,
+    Trunks
+)
diff --git a/twilio/rest/resources/trunking/credential_lists.py b/twilio/rest/resources/trunking/credential_lists.py
new file mode 100644
index 0000000000..027063ef09
--- /dev/null
+++ b/twilio/rest/resources/trunking/credential_lists.py
@@ -0,0 +1,59 @@
+from .. import NextGenInstanceResource, NextGenListResource
+
+
+class CredentialList(NextGenInstanceResource):
+    """
+    A Credential List Resource.
+    See the `SIP Trunking API reference
+    <https://www.twilio.com/docs/sip-trunking/rest/credential-lists>_`
+    for more information.
+
+    .. attribute:: sid
+
+        The unique ID for this Credential List.
+
+    .. attribute:: trunk_sid
+
+        The unique ID of the Trunk that owns this Credential List.
+    """
+
+    def delete(self):
+        """
+        Disassociates a Credential List from the trunk.
+        """
+        return self.parent.delete_instance(self.name)
+
+
+class CredentialLists(NextGenListResource):
+    """ A list of Credential List resources """
+
+    name = "CredentialLists"
+    instance = CredentialList
+    key = "credential_lists"
+
+    def list(self, **kwargs):
+        """
+        Retrieve the list of Credential List resources for a given trunk sid.
+        :param Page: The subset of results that needs to be fetched
+        :param PageSize: The size of the Page that needs to be fetched
+        """
+        return super(CredentialLists, self).list(**kwargs)
+
+    def create(self, credential_list_sid):
+        """
+        Associate a Credential List with a Trunk.
+
+        :param credential_list_sid: A human readable Credential list sid.
+        """
+        data = {
+            'credential_list_sid': credential_list_sid
+        }
+        return self.create_instance(data)
+
+    def delete(self, credential_list_sid):
+        """
+        Disassociates a Credential List from the Trunk.
+
+        :param credential_list_sid: A human readable Credential list sid.
+        """
+        return self.delete_instance(credential_list_sid)
diff --git a/twilio/rest/resources/trunking/ip_access_control_lists.py b/twilio/rest/resources/trunking/ip_access_control_lists.py
new file mode 100644
index 0000000000..73d2656f4e
--- /dev/null
+++ b/twilio/rest/resources/trunking/ip_access_control_lists.py
@@ -0,0 +1,61 @@
+from .. import NextGenInstanceResource, NextGenListResource
+
+
+class IpAccessControlList(NextGenInstanceResource):
+    """
+    An IP Access Control List Resource.
+    See the `SIP Trunking API reference
+    <https://www.twilio.com/docs/sip-trunking/rest/ip-access-control-lists>_`
+    for more information.
+
+    .. attribute:: sid
+
+        The unique ID for this IP Access Control List.
+
+    .. attribute:: trunk_sid
+
+        The unique ID of the Trunk that owns this Credential List.
+    """
+
+    def delete(self):
+        """
+        Disassociate an Ip Access Control List.
+        """
+        return self.parent.delete_instance(self.name)
+
+
+class IpAccessControlLists(NextGenListResource):
+    """ A list of IP Access Control List resources """
+
+    name = "IpAccessControlLists"
+    instance = IpAccessControlList
+    key = "ip_access_control_lists"
+
+    def list(self, **kwargs):
+        """
+        Retrieve the IP Access Control List resources.
+        :param Page: The subset of results that needs to be fetched
+        :param PageSize: The size of the Page that needs to be fetched
+        """
+        return super(IpAccessControlLists, self).list(**kwargs)
+
+    def create(self, ip_access_control_list_sid):
+        """
+        Associate an IP Access Control List with a Trunk.
+
+        :param ip_access_control_list_sid:
+            A human readable IP Access Control list sid.
+        """
+        data = {
+            'ip_access_control_list_sid': ip_access_control_list_sid
+        }
+        return self.create_instance(data)
+
+    def delete(self, ip_access_control_list_sid):
+        """
+        Disassociate an Ip Access Control List from the Trunk.
+
+        :param ip_access_control_list_sid:
+            A human readable IP Access Control list sid.
+        """
+        return self.delete_instance(ip_access_control_list_sid)
diff --git a/twilio/rest/resources/trunking/origination_urls.py b/twilio/rest/resources/trunking/origination_urls.py
new file mode 100644
index 0000000000..6cb6d4a0c2
--- /dev/null
+++ b/twilio/rest/resources/trunking/origination_urls.py
@@ -0,0 +1,89 @@
+from .. import NextGenInstanceResource, NextGenListResource
+
+
+class OriginationUrl(NextGenInstanceResource):
+    """
+    An Origination URL resource.
+    See the `TaskRouter API reference
+    <https://www.twilio.com/docs/sip-trunking/rest/origination-urls>_`
+    for more information.
+
+    .. attribute:: sid
+
+        The unique ID for this Origination URL.
+
+    .. attribute:: trunk_sid
+
+        The unique ID of the Trunk that owns this Credential List.
+    """
+
+    def delete(self):
+        """
+        Delete an Origination URL.
+        """
+        return self.parent.delete_instance(self.name)
+
+    def update(self, **kwargs):
+        """
+        Update an Origination URL.
+        """
+        return self.parent.update_instance(self.name, kwargs)
+
+
+class OriginationUrls(NextGenListResource):
+    """ A list of Origination URL resources """
+
+    name = "OriginationUrls"
+    instance = OriginationUrl
+    key = "origination_urls"
+
+    def create(self, friendly_name, sip_url, priority, weight, enabled):
+        """
+        Create a Origination URL.
+
+        :param friendly_name: A human readable descriptive text, up to 64
+            characters long.
+        :param sip_url: The SIP address you want Twilio to route your
+            Origination calls to. This must be a sip: schema.
+        :param priority: Priority ranks the importance of the URI. Value
+            ranges from 0 - 65535.
+        :param weight: Weight is used to determine the share of load when
+            more than one URI has the same priority.
+            Value ranges from 0 - 65535.
+        :param enabled: A boolean value indicating whether the URL is
+            enabled or disabled.
+
+        """
+        data = {
+            'friendly_name': friendly_name,
+            'sip_url': sip_url,
+            'priority': priority,
+            'weight': weight,
+            'enabled': enabled
+        }
+        return self.create_instance(data)
+
+    def list(self, **kwargs):
+        """
+        Retrieve the list of Origination URL resources for a given trunk sid.
+        :param Page: The subset of results that needs to be fetched
+        :param PageSize: The size of the Page that needs to be fetched
+        """
+        return super(OriginationUrls, self).list(**kwargs)
+
+    def update(self, origination_url_sid, data):
+        """
+        Update an Origination Url.
+
+        :param origination_url_sid: A human readable Origination Url sid.
+        :param data: Attributes that needs to be updated.
+        """
+        return self.update_instance(origination_url_sid, data)
+
+    def delete(self, origination_url_sid):
+        """
+        Delete an Origination Url.
+
+        :param origination_url_sid: A human readable Origination Url sid.
+        """
+        return self.delete_instance(origination_url_sid)
diff --git a/twilio/rest/resources/trunking/phone_numbers.py b/twilio/rest/resources/trunking/phone_numbers.py
new file mode 100644
index 0000000000..90c3188160
--- /dev/null
+++ b/twilio/rest/resources/trunking/phone_numbers.py
@@ -0,0 +1,59 @@
+from .. import NextGenInstanceResource, NextGenListResource
+
+
+class PhoneNumber(NextGenInstanceResource):
+    """
+    A Phone Number resource.
+    See the `TaskRouter API reference
+    <https://www.twilio.com/docs/sip-trunking/rest/phone-numbers>_`
+    for more information.
+
+    .. attribute:: sid
+
+        The unique ID for this Phone Number.
+
+    .. attribute:: trunk_sid
+
+        The unique ID of the Trunk that owns this Phone Number.
+    """
+
+    def delete(self):
+        """
+        Removes an associated Phone Number from a Trunk.
+        """
+        return self.parent.delete_instance(self.name)
+
+
+class PhoneNumbers(NextGenListResource):
+    """ A list of Phone Numbers resources """
+
+    name = "PhoneNumbers"
+    instance = PhoneNumber
+    key = "phone_numbers"
+
+    def list(self, **kwargs):
+        """
+        Retrieves the list of Phone Number resources for a given trunk sid.
+        :param Page: The subset of results that needs to be fetched
+        :param PageSize: The size of the Page that needs to be fetched
+        """
+        return super(PhoneNumbers, self).list(**kwargs)
+
+    def create(self, phone_number_sid):
+        """
+        Associates a Phone Number with the given Trunk.
+
+        :param phone_number_sid:
+        Associates a Phone Number with the given trunk.
+        """
+        data = {
+            'phone_number_sid': phone_number_sid
+        }
+        return self.create_instance(data)
+
+    def delete(self, sid):
+        """
+        Disassociates a phone number from the trunk.
+        :param sid: Phone Number Sid
+        """
+        return self.delete_instance(sid)
diff --git a/twilio/rest/resources/trunking/trunks.py b/twilio/rest/resources/trunking/trunks.py
new file mode 100644
index 0000000000..024f9777b1
--- /dev/null
+++ b/twilio/rest/resources/trunking/trunks.py
@@ -0,0 +1,65 @@
+from .. import NextGenInstanceResource, NextGenListResource
+
+
+class Trunk(NextGenInstanceResource):
+    """
+    A Trunk resource.
+    See the `TaskRouter API reference
+    <https://www.twilio.com/docs/sip-trunking/rest/trunks>_`
+    for more information.
+
+    .. attribute:: sid
+
+        The unique ID for this Trunk.
+    """
+
+    def delete(self):
+        """
+        Deletes a Trunk.
+        """
+        return self.parent.delete_instance(self.name)
+
+    def update(self, **kwargs):
+        """
+        Updates a Trunk.
+
+        """
+        return self.parent.update_instance(self.name, **kwargs)
+
+
+class Trunks(NextGenListResource):
+    """ A list of Trunk resources """
+
+    name = "Trunks"
+    instance = Trunk
+    key = "trunks"
+
+    def list(self, **kwargs):
+        """
+        Retrieve the list of Trunk resources.
+
+        :param Page: The subset of results that needs to be fetched
+        :param PageSize: The size of the Page that needs to be fetched
+        """
+        return super(Trunks, self).list(**kwargs)
+
+    def create(self, **kwargs):
+        """
+        Creates a Trunk.
+        """
+        return self.create_instance(kwargs)
+
+    def update(self, sid, body):
+        """
+        Updates a Trunk.
+        :param sid: A human readable 34 character unique identifier
+        :param body: Request body
+        """
+        return self.update_instance(sid, body)
+
+    def delete(self, sid):
+        """
+        Deletes a Trunk.
+        :param sid: A human readable 34 character unique identifier
+        """
+        return self.delete_instance(sid)
diff --git a/twilio/rest/task_router.py b/twilio/rest/task_router.py
index f9672d2820..eb93b29fd4 100644
--- a/twilio/rest/task_router.py
+++ b/twilio/rest/task_router.py
@@ -25,12 +25,13 @@ class TwilioTaskRouterClient(TwilioClient):
 
     def __init__(self, account=None, token=None,
                  base="https://taskrouter.twilio.com", version="v1",
-                 timeout=UNSET_TIMEOUT):
+                 timeout=UNSET_TIMEOUT, request_account=None):
         """
         Create a Twilio REST API client.
         """
         super(TwilioTaskRouterClient, self).__init__(account, token, base,
-                                                     version, timeout)
+                                                     version, timeout,
+                                                     request_account)
         self.base_uri = "{0}/{1}".format(base, version)
         self.workspace_uri = "{0}/Workspaces".format(self.base_uri)
 
@@ -61,6 +62,15 @@ def reservations(self, workspace_sid, task_sid):
                                               workspace_sid, task_sid)
         return Reservations(base_uri, self.auth, self.timeout)
 
+    def worker_reservations(self, workspace_sid, worker_sid):
+        """
+        Return a :class:`Reservations` instance for the :class:`Reservation`
+        with the given workspace_sid ans worker_sid
+        """
+        base_uri = "{0}/{1}/Workers/{2}".format(self.workspace_uri,
+                                                workspace_sid, worker_sid)
+        return Reservations(base_uri, self.auth, self.timeout)
+
     def task_queues(self, workspace_sid):
         """
         Return a :class:`TaskQueues` instance for the :class:`TaskQueue` with
diff --git a/twilio/rest/trunking.py b/twilio/rest/trunking.py
new file mode 100644
index 0000000000..b733db5268
--- /dev/null
+++ b/twilio/rest/trunking.py
@@ -0,0 +1,71 @@
+from twilio.rest.base import TwilioClient
+from twilio.rest.resources.trunking import (
+    CredentialLists,
+    IpAccessControlLists,
+    OriginationUrls,
+    PhoneNumbers,
+    Trunks
+)
+from twilio.rest.resources import UNSET_TIMEOUT
+
+
+class TwilioTrunkingClient(TwilioClient):
+    """
+    A client for accessing the Twilio Trunking API
+
+    :param str account: Your Account SID from `your dashboard
+        <https://twilio.com/user/account>`_
+    :param str token: Your Auth Token from `your dashboard
+        <https://twilio.com/user/account>`_
+    :param float timeout: The socket and read timeout for requests to Twilio
+    """
+
+    def __init__(self, account=None, token=None,
+                 base="https://trunking.twilio.com", version="v1",
+                 timeout=UNSET_TIMEOUT, request_account=None):
+        """
+        Create a Twilio REST API client.
+        """
+        super(TwilioTrunkingClient, self).__init__(account, token, base,
+                                                   version, timeout,
+                                                   request_account)
+        self.trunk_base_uri = "{0}/{1}".format(base, version)
+
+    def credential_lists(self, trunk_sid):
+        """
+        Return a :class:`CredentialList` instance
+        """
+        credential_lists_uri = "{0}/Trunks/{1}".format(
+            self.trunk_base_uri, trunk_sid)
+        return CredentialLists(credential_lists_uri, self.auth, self.timeout)
+
+    def ip_access_control_lists(self, trunk_sid):
+        """
+        Return a :class:`IpAccessControlList` instance
+        """
+        ip_access_control_lists_uri = "{0}/Trunks/{1}".format(
+            self.trunk_base_uri, trunk_sid)
+        return IpAccessControlLists(ip_access_control_lists_uri, self.auth,
+                                    self.timeout)
+
+    def origination_urls(self, trunk_sid):
+        """
+        Return a :class:`OriginationUrls` instance
+        """
+        origination_urls_uri = "{0}/Trunks/{1}".format(
+            self.trunk_base_uri, trunk_sid)
+        return OriginationUrls(origination_urls_uri, self.auth, self.timeout)
+
+    def phone_numbers(self, trunk_sid):
+        """
+        Return a :class:`PhoneNumbers` instance
+        """
+        phone_numbers_uri = "{0}/Trunks/{1}".format(self.trunk_base_uri,
+                                                    trunk_sid)
+        return PhoneNumbers(phone_numbers_uri, self.auth, self.timeout)
+
+    def trunks(self):
+        """
+        Return a :class:`Trunks` instance
+        """
+        return Trunks(self.trunk_base_uri, self.auth, self.timeout)
diff --git a/twilio/task_router/__init__.py b/twilio/task_router/__init__.py
index 17975c1cba..ae892eda49 100644
--- a/twilio/task_router/__init__.py
+++ b/twilio/task_router/__init__.py
@@ -1,120 +1,206 @@
 import time
-
 from .. import jwt
+from .taskrouter_config import TaskRouterConfig
+from .workflow_config import WorkflowConfig
+from .workflow_ruletarget import WorkflowRuleTarget
+from .workflow_rule import WorkflowRule
 
+import warnings
+warnings.simplefilter('always', DeprecationWarning)
 
 TASK_ROUTER_BASE_URL = 'https://taskrouter.twilio.com'
-TASK_ROUTER_BASE_WS_URL = 'https://event-bridge.twilio.com/v1/wschannels'
+TASK_ROUTER_BASE_EVENTS_URL = 'https://event-bridge.twilio.com/v1/wschannels'
+TASK_ROUTER_VERSION = "v1"
 
 REQUIRED = {'required': True}
 OPTIONAL = {'required': False}
 
 
+def deprecated(func):
+    def log_warning(*args, **kwargs):
+        # stacklevel = 2 makes the warning refer to the caller of the
+        # deprecation rather than the source of deprecation itself
+        warnings.warn("Call to deprecated function {0}.".
+                      format(func.__name__),
+                      stacklevel=2,
+                      category=DeprecationWarning)
+        return func(*args, **kwargs)
+    return log_warning
+
+
 class TaskRouterCapability(object):
-    """
-    A token to control permissions for the TaskRouter service.
-
-    :param str account_sid: The account to generate a token for
-    :param str auth_token: The auth token for the account. Used to sign the
-        token and will not be included in generated output.
-    :param str workspace_sid: The workspace to grant capabilities over
-    :param str worker_sid: The worker sid to grant capabilities over
-    :param str base_url: The base TaskRouter API URL
-    :param str base_ws_url: The base TaskRouter event stream URL
-    """
-    def __init__(self, account_sid, auth_token, workspace_sid, worker_sid,
-                 base_url=TASK_ROUTER_BASE_URL,
-                 version='v1',
-                 base_ws_url=TASK_ROUTER_BASE_WS_URL):
+    def __init__(self, account_sid, auth_token, workspace_sid, channel_id):
         self.account_sid = account_sid
         self.auth_token = auth_token
-        self.workspace_sid = workspace_sid
-        self.worker_sid = worker_sid
-        self.version = version
-        self.base_url = '%s/%s' % (base_url, self.version)
-        self.base_ws_url = base_ws_url
         self.policies = []
 
-        self._allow_worker_websocket_urls()
-        self._allow_activity_list_fetch()
+        self.workspace_sid = workspace_sid
+        self.channel_id = channel_id
+        self.base_url = "{0}/{1}/Workspaces/{2}".format(TASK_ROUTER_BASE_URL,
+                                                        TASK_ROUTER_VERSION,
+                                                        workspace_sid)
 
-    @property
-    def workspace_url(self):
-        return '%s/Workspaces/%s' % (self.base_url, self.workspace_sid)
+        # validate the JWT
+        self.validate_jwt()
+
+        # add permissions to GET and POST to the event-bridge channel
+        self.allow_web_sockets(channel_id)
+
+        # set up resources
+        self.setup_resource()
+
+        # add permissions to fetch the instance resource
+        self.add_policy(self.resource_url, "GET", True)
 
     @property
-    def worker_url(self):
-        return '%s/Workers/%s' % (self.workspace_url, self.worker_sid)
-
-    def _allow_worker_websocket_urls(self):
-        worker_event_url = '%s/%s/%s' % (
-            self.base_ws_url,
-            self.account_sid,
-            self.worker_sid,
-        )
-        self.policies.append(make_policy(
-            worker_event_url,
-            'GET',
-        ))
-        self.policies.append(make_policy(
-            worker_event_url,
-            'POST',
-        ))
+    def channel_prefix(self):
+        return self.channel_id[0:2]
 
-    def _allow_activity_list_fetch(self):
-        self.policies.append(make_policy(
-            '%s/Activities' % self.workspace_url,
-            'GET',
-        ))
+    def setup_resource(self):
+        if self.channel_prefix == "WS":
+            self.resource_url = self.base_url
+        elif self.channel_prefix == "WK":
+            self.resource_url = self.base_url + "/Workers/" + self.channel_id
 
-    def allow_worker_activity_updates(self):
-        self.policies.append(make_policy(
-            self.worker_url,
-            'POST',
-            post_filter={'ActivitySid': REQUIRED},
-        ))
+            activity_url = self.base_url + "/Activities"
+            self.allow(activity_url, "GET")
+
+            tasks_url = self.base_url + "/Tasks/**"
+            self.allow(tasks_url, "GET")
+
+            worker_reservations_url = self.resource_url + "/Reservations/**"
+            self.allow(worker_reservations_url, "GET")
 
+        elif self.channel_prefix == "WQ":
+            self.resource_url = "{0}/TaskQueues/{1}".format(
+                self.base_url, self.channel_id)
+
+    def allow_web_sockets(self, channel_id):
+        web_socket_url = "{0}/{1}/{2}".format(TASK_ROUTER_BASE_EVENTS_URL,
+                                              self.account_sid,
+                                              self.channel_id)
+
+        self.policies.append(self.make_policy(web_socket_url, "GET", True))
+        self.policies.append(self.make_policy(web_socket_url, "POST", True))
+
+    def validate_jwt(self):
+        if self.account_sid is None or self.account_sid[0:2] != "AC":
+            raise ValueError('Invalid AccountSid provided: ' +
+                             self.account_sid)
+        if self.workspace_sid is None or self.workspace_sid[0:2] != "WS":
+            raise ValueError('Invalid WorkspaceSid provided: ' +
+                             self.workspace_sid)
+        if self.channel_id is None:
+            raise ValueError('ChannelId not provided')
+
+        if self.channel_prefix != "WS" and self.channel_prefix != "WK" \
+                and self.channel_prefix != "WQ":
+            raise ValueError('Invalid ChannelId provided: ' + self.channel_id)
+
+    def allow_fetch_subresources(self):
+        self.allow(self.resource_url + "/**", "GET")
+
+    def allow_updates(self):
+        self.allow(self.resource_url, "POST")
+
+    def allow_updates_subresources(self):
+        self.allow(self.resource_url + "/**", "POST")
+
+    def allow_delete(self):
+        self.allow(self.resource_url, "DELETE")
+
+    def allow_delete_subresources(self):
+        self.allow(self.resource_url + "/**", "DELETE")
+
+    @deprecated
     def allow_worker_fetch_attributes(self):
-        self.policies.append(make_policy(
-            self.worker_url,
-            'GET',
-        ))
+        if self.channel_prefix != "WK":
+            raise ValueError("Deprecated func not applicable to non Worker")
+        else:
+            self.policies.append(self.make_policy(
+                self.resource_url,
+                'GET'))
 
+    @deprecated
+    def allow_worker_activity_updates(self):
+        if self.channel_prefix == "WK":
+            self.policies.append(self.make_policy(
+                self.resource_url,
+                'POST',
+                True,
+                post_filter={'ActivitySid': REQUIRED}))
+        else:
+            raise ValueError("Deprecated func not applicable to non Worker")
+
+    @deprecated
     def allow_task_reservation_updates(self):
-        tasks_url = '%s/Tasks/**' % self.workspace_url
-        self.policies.append(make_policy(
-            tasks_url,
-            'POST',
-            post_filter={'ReservationStatus': REQUIRED},
-        ))
+        if self.channel_prefix == "WK":
+            tasks_url = self.base_url + "/Tasks/**"
+            self.policies.append(self.make_policy(
+                tasks_url,
+                'POST',
+                True))
+        else:
+            raise ValueError("Deprecated func not applicable to non Worker")
 
-    def generate_token(self, ttl=3600, attributes=None):
-        """
-        Generate a token string based on the credentials and permissions
-        previously configured on this object.
+    def add_policy(self, url, method,
+                   allowed, query_filter=None, post_filter=None):
+
+        policy = self.make_policy(url, method,
+                                  allowed, query_filter, post_filter)
+        self.policies.append(policy)
+
+    def allow(self, url, method, query_filter=None, post_filter=None):
+        self.add_policy(url, method, True, query_filter, post_filter)
+
+    def deny(self, url, method, query_filter=None, post_filter=None):
+        self.add_policy(url, method, False, query_filter, post_filter)
 
-        :param int ttl: Expiration time in seconds of the token. Defaults to
-            3600 seconds (1 hour).
-        :param dict attributes: Extra attributes to pass into the token.
+    def make_policy(self, url, method,
+                    allowed=True, query_filter=None, post_filter=None):
+
+        """Create a policy dictionary for the given resource and method.
+        :param str url: the resource URL to grant or deny access to
+        :param str method: the HTTP method to allow or deny
+        :param allowed bool: whether this request is allowed
+        :param dict query_filter: specific GET parameter names
+            to require or allow
+        :param dict post_filter: POST parameter names
+            to require or allow
         """
 
-        return self._generate_token(
-            ttl,
-            {
-                'account_sid': self.account_sid,
-                'channel': self.worker_sid,
-                'worker_sid': self.worker_sid,
-                'workspace_sid': self.workspace_sid,
-            }
-        )
+        return {
+            'url': url,
+            'method': method,
+            'allow': allowed,
+            'query_filter': query_filter or {},
+            'post_filter': post_filter or {}
+        }
+
+    def get_resource_url(self):
+        return self.resource_url
+
+    def generate_token(self, ttl=3600):
+        task_router_attributes = {
+            'account_sid': self.account_sid,
+            'workspace_sid': self.workspace_sid,
+            'channel': self.channel_id
+        }
+
+        if self.channel_prefix == "WK":
+            task_router_attributes["worker_sid"] = self.channel_id
+        elif self.channel_prefix == "WQ":
+            task_router_attributes["taskqueue_sid"] = self.channel_id
+
+        return self._generate_token(ttl, task_router_attributes)
 
     def _generate_token(self, ttl, attributes=None):
         payload = {
-            'version': self.version,
-            'friendly_name': self.worker_sid,
-            'policies': self.policies,
             'iss': self.account_sid,
             'exp': int(time.time()) + ttl,
+            'version': TASK_ROUTER_VERSION,
+            'friendly_name': self.channel_id,
+            'policies': self.policies,
         }
 
         if attributes is not None:
@@ -123,22 +209,55 @@ def _generate_token(self, ttl, attributes=None):
         return jwt.encode(payload, self.auth_token, 'HS256')
 
 
-def make_policy(url, method, query_filter=None, post_filter=None,
-                allowed=True):
-    """
-    Create a policy dictionary for the given resource and method.
-
-    :param str url: the resource URL to grant or deny access to
-    :param str method: the HTTP method to allow or deny
-    :param dict query_filter: specific GET parameter names to require or allow
-    :param dict post_filter: POST parameter names to require or allow
-    :param allowed bool: whether this request is allowed
-    """
-
-    return {
-        'url': url,
-        'method': method,
-        'allow': allowed,
-        'query_filter': query_filter or {},
-        'post_filter': post_filter or {},
-    }
+class TaskRouterWorkerCapability(TaskRouterCapability):
+    def __init__(self, account_sid, auth_token, workspace_sid, worker_sid):
+        super(TaskRouterWorkerCapability, self).__init__(account_sid,
+                                                         auth_token,
+                                                         workspace_sid,
+                                                         worker_sid)
+
+        self.activity_url = self.base_url + "/Activities"
+        self.reservations_url = self.base_url + "/Tasks/**"
+        self.worker_reservations_url = self.resource_url + "/Reservations/**"
+
+        # add permissions to fetch the
+        # list of activities, tasks, and worker reservations
+        self.allow(self.activity_url, "GET")
+        self.allow(self.reservations_url, "GET")
+        self.allow(self.worker_reservations_url, "GET")
+
+    def setup_resource(self):
+        self.resource_url = self.base_url + "/Workers/" + self.channel_id
+
+    def allow_activity_updates(self):
+        self.policies.append(self.make_policy(
+            self.resource_url,
+            'POST',
+            True,
+            post_filter={'ActivitySid': REQUIRED}))
+
+    def allow_reservation_updates(self):
+        self.policies.append(self.make_policy(
+            self.reservations_url,
+            'POST',
+            True))
+        self.policies.append(self.make_policy(
+            self.worker_reservations_url,
+            'POST',
+            True))
+
+
+class TaskRouterTaskQueueCapability(TaskRouterCapability):
+    def setup_resource(self):
+        self.resource_url = self.base_url + "/TaskQueues/" + self.channel_id
+
+
+class TaskRouterWorkspaceCapability(TaskRouterCapability):
+    def __init__(self, account_sid, auth_token, workspace_sid):
+        super(TaskRouterWorkspaceCapability, self).__init__(account_sid,
+                                                            auth_token,
+                                                            workspace_sid,
+                                                            workspace_sid)
+
+    def setup_resource(self):
+        self.resource_url = self.base_url
diff --git a/twilio/task_router/taskrouter_config.py b/twilio/task_router/taskrouter_config.py
new file mode 100644
index 0000000000..22c04f1913
--- /dev/null
+++ b/twilio/task_router/taskrouter_config.py
@@ -0,0 +1,23 @@
+from .workflow_rule import WorkflowRule
+from .workflow_ruletarget import WorkflowRuleTarget
+
+
+class TaskRouterConfig:
+
+    """
+        TaskRouterConfig represents the filter and default_filter
+        of a workflow configuration of taskrouter
+    """
+
+    def __init__(self, rules, default_target):
+        self.filters = rules
+        self.default_filter = default_target
+
+        for rule in self.filters:
+            if not isinstance(rule, WorkflowRule):
+                filter_friendly_name = rule.pop('filter_friendly_name', None)
+                if filter_friendly_name is not None:
+                    rule['friendly_name'] = filter_friendly_name
+
+    def __repr__(self):
+        return self.__dict__
diff --git a/twilio/task_router/workflow_config.py b/twilio/task_router/workflow_config.py
new file mode 100644
index 0000000000..e9c27aa378
--- /dev/null
+++ b/twilio/task_router/workflow_config.py
@@ -0,0 +1,26 @@
+from .taskrouter_config import TaskRouterConfig
+import json
+
+
+class WorkflowConfig:
+
+    """
+    WorkflowConfig represents the whole workflow config json which contains
+    filters and default_filter.
+    """
+
+    def __init__(self, workflow_rules, default_target):
+        # filters and default_filters
+        self.task_routing = TaskRouterConfig(workflow_rules, default_target)
+
+    def to_json(self):
+        return json.dumps(self,
+                          default=lambda o: o.__dict__,
+                          sort_keys=True,
+                          indent=4)
+
+    @staticmethod
+    def json2obj(data):
+        m = json.loads(data)
+        return WorkflowConfig(m['task_routing']['filters'],
+                              m['task_routing']['default_filter'])
diff --git a/twilio/task_router/workflow_rule.py b/twilio/task_router/workflow_rule.py
new file mode 100644
index 0000000000..5c4c1fd71c
--- /dev/null
+++ b/twilio/task_router/workflow_rule.py
@@ -0,0 +1,34 @@
+from .workflow_ruletarget import WorkflowRuleTarget
+
+
+class WorkflowRule:
+
+    """
+    WorkflowRule represents the top level filter
+    which contains a 1 or more targets
+
+    ..attribute::expression
+
+       The expression at the top level filter
+
+    ..attribute::targets
+
+       The list of targets under the filter
+
+    ..attribute::friendlyName
+
+       The name of the filter
+    """
+
+    def __init__(self, expression, targets, friendly_name):
+
+        self.expression = expression
+        self.targets = targets
+        self.friendly_name = friendly_name
+
+    def __repr__(self):
+        return str({
+            'expression': self.expression,
+            'friendly_name': self.friendly_name,
+            'targets': self.targets,
+        })
diff --git a/twilio/task_router/workflow_ruletarget.py b/twilio/task_router/workflow_ruletarget.py
new file mode 100644
index 0000000000..1cee506c30
--- /dev/null
+++ b/twilio/task_router/workflow_ruletarget.py
@@ -0,0 +1,27 @@
+class WorkflowRuleTarget:
+    """
+    Workflow Rule target which is encompassed
+    inside targets
+
+    ..attribute::queue
+
+      The queue which will handle the task matching this filter target
+
+    ..attribute::expression
+
+       The dynamic expression if any for this matching
+
+    ..attribute::priority
+
+      The priority for the target
+
+    ..attribute::timeout
+
+      The timeout before the reservation expires.
+    """
+    def __init__(self, queue, expression, priority, timeout):
+
+        self.queue = queue
+        self.expression = expression
+        self.priority = priority
+        self.timeout = timeout
diff --git a/twilio/twiml.py b/twilio/twiml.py
index f05015842d..cbac1122da 100644
--- a/twilio/twiml.py
+++ b/twilio/twiml.py
@@ -524,10 +524,25 @@ class Enqueue(Verb):
     GET = 'GET'
     POST = 'POST'
 
+    nestables = ['Task']
+
     def __init__(self, name, **kwargs):
         super(Enqueue, self).__init__(**kwargs)
         self.body = name
 
+    def task(self, attributes, **kwargs):
+        return self.append(Task(attributes, **kwargs))
+
+
+class Task(Verb):
+    """Specify the task attributes when enqueuing a call
+
+    :param attributes: Attributes for a task
+    """
+    def __init__(self, attributes, **kwargs):
+        super(Task, self).__init__(**kwargs)
+        self.body = attributes
+
 
 class Leave(Verb):
     """Signals the call to leave its queue
diff --git a/twilio/version.py b/twilio/version.py
index 953ebe5125..fe171cc661 100644
--- a/twilio/version.py
+++ b/twilio/version.py
@@ -1,2 +1,2 @@
-__version_info__ = ('4', '4', '0')
+__version_info__ = ('5', '6', '0')
 __version__ = '.'.join(__version_info__)