diff --git a/endpoints/api/__init__.py b/endpoints/api/__init__.py
index a281b075a..5fd2d7845 100644
--- a/endpoints/api/__init__.py
+++ b/endpoints/api/__init__.py
@@ -181,6 +181,8 @@ import endpoints.api.repository
 import endpoints.api.repotoken
 import endpoints.api.search
 import endpoints.api.tag
+import endpoints.api.team
 import endpoints.api.trigger
+import endpoints.api.organization
 import endpoints.api.user
 import endpoints.api.webhook
diff --git a/endpoints/api/legacy.py b/endpoints/api/legacy.py
index 3f811f958..e3f1b3bf1 100644
--- a/endpoints/api/legacy.py
+++ b/endpoints/api/legacy.py
@@ -407,6 +407,7 @@ def team_view(orgname, team):
   }
 
 
+# Ported
 @api_bp.route('/organization/', methods=['POST'])
 @api_login_required
 @internal_api_call
@@ -454,6 +455,7 @@ def org_view(o, teams):
   return view
 
 
+# Ported
 @api_bp.route('/organization/<orgname>', methods=['GET'])
 @api_login_required
 def get_organization(orgname):
@@ -470,6 +472,7 @@ def get_organization(orgname):
   abort(403)
 
 
+# Ported
 @api_bp.route('/organization/<orgname>', methods=['PUT'])
 @api_login_required
 @org_api_call('change_user_details')
@@ -718,6 +721,7 @@ def get_organization_member(orgname, membername):
   abort(403)
 
 
+# Ported
 @api_bp.route('/organization/<orgname>/private', methods=['GET'])
 @api_login_required
 @internal_api_call
@@ -758,6 +762,7 @@ def member_view(member):
   }
 
 
+# Ported
 @api_bp.route('/organization/<orgname>/team/<teamname>',
            methods=['PUT', 'POST'])
 @api_login_required
@@ -804,6 +809,7 @@ def update_organization_team(orgname, teamname):
   abort(403)
 
 
+# Ported
 @api_bp.route('/organization/<orgname>/team/<teamname>',
            methods=['DELETE'])
 @api_login_required
@@ -817,6 +823,7 @@ def delete_organization_team(orgname, teamname):
   abort(403)
 
 
+# Ported
 @api_bp.route('/organization/<orgname>/team/<teamname>/members',
            methods=['GET'])
 @api_login_required
@@ -840,6 +847,7 @@ def get_organization_team_members(orgname, teamname):
   abort(403)
 
 
+# Ported
 @api_bp.route('/organization/<orgname>/team/<teamname>/members/<membername>',
            methods=['PUT', 'POST'])
 @api_login_required
@@ -869,6 +877,7 @@ def update_organization_team_member(orgname, teamname, membername):
   abort(403)
 
 
+# Ported
 @api_bp.route('/organization/<orgname>/team/<teamname>/members/<membername>',
            methods=['DELETE'])
 @api_login_required
diff --git a/endpoints/api/organization.py b/endpoints/api/organization.py
new file mode 100644
index 000000000..235f36ebf
--- /dev/null
+++ b/endpoints/api/organization.py
@@ -0,0 +1,184 @@
+import logging
+import stripe
+
+from flask import request
+from flask.ext.restful import abort
+
+from endpoints.api import resource, nickname, ApiResource, validate_json_request, request_error
+from endpoints.api.team import team_view
+from auth.permissions import (AdministerOrganizationPermission,  OrganizationMemberPermission,
+                              CreateRepositoryPermission)
+from auth.auth_context import get_authenticated_user
+from data import model
+from data.plans import get_plan
+from util.gravatar import compute_hash
+
+
+logger = logging.getLogger(__name__)
+
+
+
+def org_view(o, teams):
+  admin_org = AdministerOrganizationPermission(o.username)
+  is_admin = admin_org.can()
+  view = {
+    'name': o.username,
+    'email': o.email if is_admin else '',
+    'gravatar': compute_hash(o.email),
+    'teams': {t.name : team_view(o.username, t) for t in teams},
+    'is_admin': is_admin
+  }
+
+  if is_admin:
+    view['invoice_email'] = o.invoice_email
+
+  return view
+
+
+@resource('/v1/organization/', methods=['POST'])
+class OrganizationList(ApiResource):
+  """ Resource for creating organizations. """
+  schemas = {
+    'NewOrg': {
+      'id': 'NewOrg',
+      'type': 'object',
+      'description': 'Description of a new organization.',
+      'required': True,
+      'properties': {
+        'name': {
+          'type': 'string',
+          'description': 'Organization username',
+          'required': True,
+        },
+        'email': {
+          'type': 'string',
+          'description': 'Organization contact email',
+          'required': True,
+        },
+      },
+    },
+  }
+
+  @nickname('createOrganization')
+  @validate_json_request('NewOrg')
+  def post(self):
+    """ Create a new organization. """
+    org_data = request.get_json()
+    existing = None
+
+    try:
+      existing = model.get_organization(org_data['name'])
+    except model.InvalidOrganizationException:
+      pass
+
+    if not existing:
+      try:
+        existing = model.get_user(org_data['name'])
+      except model.InvalidUserException:
+        pass
+
+    if existing:
+      msg = 'A user or organization with this name already exists'
+      return request_error(message=msg)
+
+    try:
+      model.create_organization(org_data['name'], org_data['email'], get_authenticated_user())
+      return 'Created', 201
+    except model.DataModelException as ex:
+      return request_error(exception=ex)
+
+
+@resource('/v1/organization/<orgname>', methods=['GET'])
+class Organization(ApiResource):
+  """ Resource for managing organizations. """
+  schemas = {
+    'UpdateOrg': {
+      'id': 'UpdateOrg',
+      'type': 'object',
+      'description': 'Description of updates for an existing organization',
+      'required': True,
+      'properties': {
+        'email': {
+          'type': 'string',
+          'description': 'Organization contact email',
+        },
+        'invoice_email': {
+          'type': 'boolean',
+          'description': 'Whether the organization desires to receive emails for invoices',
+        },
+      },
+    },
+  }
+  @nickname('getOrganization')
+  def get(self, orgname):
+    """ Get the details for the specified organization """
+    permission = OrganizationMemberPermission(orgname)
+    if permission.can():
+      try:
+        org = model.get_organization(orgname)
+      except model.InvalidOrganizationException:
+        abort(404)
+
+      teams = model.get_teams_within_org(org)
+      return org_view(org, teams)
+
+    abort(403)
+
+  # @org_api_call('change_user_details')
+  @nickname('changeOrganizationDetails')
+  @validate_json_request('UpdateOrg')
+  def put(self, orgname):
+    """ Change the details for the specified organization. """
+    try:
+      org = model.get_organization(orgname)
+    except model.InvalidOrganizationException:
+      abort(404)
+      
+    org_data = request.get_json()
+    if 'invoice_email' in org_data:
+      logger.debug('Changing invoice_email for organization: %s', org.username)
+      model.change_invoice_email(org, org_data['invoice_email'])
+
+    if 'email' in org_data and org_data['email'] != org.email:
+      new_email = org_data['email']
+      if model.find_user_by_email(new_email):
+        return request_error(message='E-mail address already used')
+      
+      logger.debug('Changing email address for organization: %s', org.username)
+      model.update_email(org, new_email)
+      
+    teams = model.get_teams_within_org(org)
+    return org_view(org, teams)
+
+
+@resource('/v1/organization/<orgname>/private')
+class OrgPrivateRepositories(ApiResource):
+  """ Custom verb to compute whether additional private repositories are available. """
+  # @org_api_call('get_user_private_allowed')
+  @nickname('getOrganizationPrivateAllowed')
+  def get(self, orgname):
+    permission = CreateRepositoryPermission(orgname)
+    if permission.can():
+      organization = model.get_organization(orgname)
+      private_repos = model.get_private_repo_count(organization.username)
+      data = {
+        'privateAllowed': False
+      }
+
+      if organization.stripe_id:
+        cus = stripe.Customer.retrieve(organization.stripe_id)
+        if cus.subscription:
+          repos_allowed = 0
+          plan = get_plan(cus.subscription.plan.id)
+          if plan:
+            repos_allowed = plan['privateRepos']
+                
+          data['privateAllowed'] = (private_repos < repos_allowed)
+
+        
+      if AdministerOrganizationPermission(orgname).can():
+        data['privateCount'] = private_repos
+        
+      return data
+
+    abort(403)
\ No newline at end of file
diff --git a/endpoints/api/repotoken.py b/endpoints/api/repotoken.py
index c674f463f..ca806ad3e 100644
--- a/endpoints/api/repotoken.py
+++ b/endpoints/api/repotoken.py
@@ -31,7 +31,7 @@ class RepositoryTokenList(RepositoryParamResource):
       'properties': {
         'friendlyName': {
           'type': 'string',
-          'description': 'Friendly name to help identify the token.',
+          'description': 'Friendly name to help identify the token',
           'required': True,
         },
       },
@@ -72,7 +72,7 @@ class RepositoryToken(RepositoryParamResource):
     'TokenPermission': {
       'id': 'TokenPermission',
       'type': 'object',
-      'description': 'Description of a token permission.',
+      'description': 'Description of a token permission',
       'required': True,
       'properties': {
         'role': {
diff --git a/endpoints/api/team.py b/endpoints/api/team.py
new file mode 100644
index 000000000..147b87b98
--- /dev/null
+++ b/endpoints/api/team.py
@@ -0,0 +1,178 @@
+from flask import request
+from flask.ext.restful import abort
+
+from endpoints.api import (resource, nickname, ApiResource, validate_json_request, request_error,
+                           log_action)
+from auth.permissions import AdministerOrganizationPermission, ViewTeamPermission
+from auth.auth_context import get_authenticated_user
+from data import model
+
+
+def team_view(orgname, team):
+  view_permission = ViewTeamPermission(orgname, team.name)
+  role = model.get_team_org_role(team).name
+  return {
+    'id': team.id,
+    'name': team.name,
+    'description': team.description,
+    'can_view': view_permission.can(),
+    'role': role
+  }
+
+def member_view(member):
+  return {
+    'name': member.username,
+    'kind': 'user',
+    'is_robot': member.robot,
+  }
+
+
+@resource('/v1/organization/<orgname>/team/<teamname>',
+           methods=['PUT', 'POST'])
+class OrganizationTeam(ApiResource):
+  """ Resource for manging an organization's teams. """
+  schemas = {
+    'TeamDescription': {
+      'id': 'TeamDescription',
+      'type': 'object',
+      'description': 'Description of a team',
+      'required': True,
+      'properties': {
+        'role': {
+          'type': 'string',
+          'description': 'Org wide permissions that should apply to the team',
+          'required': True,
+          'enum': [
+            'member',
+            'creator',
+            'admin',
+          ],
+        },
+        'description': {
+          'type': 'string',
+          'description': 'Markdown description for the team',
+          'required': True,
+        },
+      },
+    },
+  }
+
+  @nickname('updateOrganizationTeam')
+  @validate_json_request('TeamDescription')
+  def put(self, orgname, teamname):
+    """ Update the org-wide permission for the specified team. """
+    edit_permission = AdministerOrganizationPermission(orgname)
+    if edit_permission.can():
+      team = None
+
+      details = request.get_json()
+      is_existing = False
+      try:
+        team = model.get_organization_team(orgname, teamname)
+        is_existing = True
+      except model.InvalidTeamException:
+        # Create the new team.
+        description = details['description'] if 'description' in details else ''
+        role = details['role'] if 'role' in details else 'member'
+
+        org = model.get_organization(orgname)
+        team = model.create_team(teamname, org, role, description)
+        log_action('org_create_team', orgname, {'team': teamname})
+
+      if is_existing:
+        if ('description' in details and
+            team.description != details['description']):
+          team.description = details['description']
+          team.save()
+          log_action('org_set_team_description', orgname,
+                     {'team': teamname, 'description': team.description})
+
+        if 'role' in details:
+          role = model.get_team_org_role(team).name
+          if role != details['role']:
+            team = model.set_team_org_permission(team, details['role'],
+                                                 get_authenticated_user().username)
+            log_action('org_set_team_role', orgname, {'team': teamname, 'role': details['role']})
+            
+      return team_view(orgname, team), 200  # 201 for post
+
+    abort(403)
+
+  @nickname('deleteOrganizationTeam')
+  def delete(self, orgname, teamname):
+    """ Delete the specified team. """
+    permission = AdministerOrganizationPermission(orgname)
+    if permission.can():
+      model.remove_team(orgname, teamname, get_authenticated_user().username)
+      log_action('org_delete_team', orgname, {'team': teamname})
+      return 'Deleted', 204
+
+    abort(403)
+
+
+@resource('/v1/organization/<orgname>/team/<teamname>/members')
+class TeamMemberList(ApiResource):
+  """ Resource for managing the list of members for a team. """
+  @nickname('getOrganizationTeamMembers')
+  def get(self, orgname, teamname):
+    """ Retrieve the list of members for the specified team. """
+    view_permission = ViewTeamPermission(orgname, teamname)
+    edit_permission = AdministerOrganizationPermission(orgname)
+
+    if view_permission.can():
+      team = None
+      try:
+        team = model.get_organization_team(orgname, teamname)
+      except model.InvalidTeamException:
+        abort(404)
+        
+      members = model.get_organization_team_members(team.id)
+      return {
+        'members': {m.username : member_view(m) for m in members},
+        'can_edit': edit_permission.can()
+      }
+
+    abort(403)
+
+
+@resource('/v1/organization/<orgname>/team/<teamname>/members/<membername>')
+class TeamMember(ApiResource):
+  """ Resource for managing individual members of a team. """
+  @nickname('updateOrganizationTeamMember')
+  def put(self, orgname, teamname, membername):
+    """ Add a member to an existing team. """
+    permission = AdministerOrganizationPermission(orgname)
+    if permission.can():
+      team = None
+      user = None
+
+      # Find the team.
+      try:
+        team = model.get_organization_team(orgname, teamname)
+      except model.InvalidTeamException:
+        abort(404)
+
+      # Find the user.
+      user = model.get_user(membername)
+      if not user:
+        return request_error(message='Unknown user')
+        
+      # Add the user to the team.
+      model.add_user_to_team(user, team)
+      log_action('org_add_team_member', orgname, {'member': membername, 'team': teamname})
+      return member_view(user)
+
+    abort(403)
+
+  @nickname('deleteOrganizationTeamMember')
+  def delete(self, orgname, teamname, membername):
+    """ Delete an existing member of a team. """
+    permission = AdministerOrganizationPermission(orgname)
+    if permission.can():
+      # Remote the user from the team.
+      invoking_user = get_authenticated_user().username
+      model.remove_user_from_team(orgname, teamname, membername, invoking_user)
+      log_action('org_remove_team_member', orgname, {'member': membername, 'team': teamname})
+      return 'Deleted', 204
+
+    abort(403)