diff --git a/modules/radar/radar_og/radar_og.module b/modules/radar/radar_og/radar_og.module
index 3d22591f1b8564f49465eff05882f03f8fec06c5..2f20c5935066e77db09a4f17f838bbec5559e7f9 100644
--- a/modules/radar/radar_og/radar_og.module
+++ b/modules/radar/radar_og/radar_og.module
@@ -1177,4 +1177,19 @@ function radar_og_entityreference_request_prepopulate($entity_type, $entity, $fi
   return $items;
 }
 
+function radar_og_can_post_request($gid, $type, $account = NULL) {
+  // Only checking non-member at the moment. There should be possibility that
+  // groups that have members who can propose, and admins who accept. Seems no
+  // one noticed this isn't an option at the moment?
 
+  static $non_member_gids = NULL;
+
+  if (is_null($non_member_gids)) {
+    $query = db_query(
+      'SELECT gid FROM {og_role_permission} p INNER JOIN {og_role} r ON r.rid = p.rid where (p.permission = :permission_1 OR p.permission = :permission_2) AND r.name = :role_name',
+      array(':permission_1' => 'subscribe', ':permission_2' => 'subscribe without approval', 'role_name' => 'non-member'));
+    $non_member_gids = $query->fetchCol();
+  }
+
+  return in_array($gid, $non_member_gids);
+}
diff --git a/modules/radar/radar_services/plugins/services_entity/resource_controller.inc b/modules/radar/radar_services/plugins/services_entity/resource_controller.inc
index 80c60359e9d8f73d895de3a706fab0848aad73d2..3abb01b38e2b9e4982f817a98b3ad5005eb4ba1a 100644
--- a/modules/radar/radar_services/plugins/services_entity/resource_controller.inc
+++ b/modules/radar/radar_services/plugins/services_entity/resource_controller.inc
@@ -1,14 +1,8 @@
 <?php
+
 /**
- * This class is designed to create a very clean API that integrates with
- * the services and entity modules. We want to strip all "drupalisms" out
- * of the API. For example, there should be no [LANGUAGE_NONE][0][value] or
- * field_ in the API.
- *
- * It should be possible to create an API that is easily replicated on another
- * system.
- *
- * Much of this code is borrowed from restws module.
+ * Ugly mash-up of Services Entity Resource Clean, Services UUID Resource, and
+ * custom submission rules.
  */
 class RadarServicesEntityResourceController extends ServicesEntityResourceControllerClean {
 
@@ -38,6 +32,42 @@ class RadarServicesEntityResourceController extends ServicesEntityResourceContro
       // Access is handled per-entity by index().
       return TRUE;
     }
+    // Further restrict Create, Delete and Update operations
+    // even if on the website ui it could be otherwise.
+    if ($op != 'view' && !user_access('services create update delete operations')) {
+      return FALSE;
+    }
+    if ($op != 'create') {
+      // Retrieve, Delete, Update.
+      list($entity_type, $entity_uuid) = $args;
+      if ($entity_type == 'location' && $op != 'view') {
+        services_error(t('If you need to edit locations via the API please contact radar@squat.net'), 403);
+      }
+      $info = entity_get_info($entity_type);
+      $uuid_key = $info['entity keys']['uuid'];
+      $ids = entity_get_id_by_uuid($entity_type, [$entity_uuid]);
+      $entity_id = reset($ids);
+
+      if (empty($entity_id)) {
+        if ($op == 'update') {
+          // Create operation with specified UUID. Check $op == 'create'.
+          // Sorry this reads like rubbish, but this is the only option here
+          // that falls through to the next part of the function the rest return
+          // something.
+          $op = 'create';
+          $args = [$entity_type, $args[2]];
+        }
+        else {
+          services_error(t('Entity not found'), 404);
+        }
+      }
+      else {
+        $entity = entity_load_single($entity_type, $entity_id);
+
+        // Pass the entity to the access control.
+        return entity_access($op, $entity_type, $entity ? $entity : NULL);
+      }
+    }
     // For create operations, we need to pass a new entity to entity_access()
     // in order to check per-bundle creation rights. For all other operations
     // we load the existing entity instead.
@@ -50,19 +80,106 @@ class RadarServicesEntityResourceController extends ServicesEntityResourceContro
       }
       // Create a wrapper from the entity so we can call its access() method.
       $wrapper = $this->createWrapperFromValues($entity_type, $data);
-      return $wrapper->entityAccess('create');      list($entity_type, $data) = $args;
+      return $wrapper->entityAccess('create');
     }
-    else {
-      // Retrieve, Delete, Update.
-      list($entity_type, $entity_id) = $args;
 
-      $entity = entity_uuid_load($entity_type, [$entity_id]);
-      $entity = reset($entity);
+  }
 
-      // Pass the entity to the access control.
-      return entity_access($op, $entity_type, $entity ? $entity : NULL);
+  public function create($entity_type, array $values) {
+    $this->formSubmissionChecks('create', $entity_type, $values);
+    $info = entity_get_info($entity_type);
+    $uuid_key = $info['entity keys']['uuid'];
+    // Mixing UUID and EntityMetadataWrapper. Need the author already with UID.
+    if (!empty($values['author']['id'])) {
+      $uids = entity_get_id_by_uuid('user', [$values['author']['id']]);
+      $values['author']['id'] = reset($uids);
     }
+    $wrapper = $this->createWrapperFromValues($entity_type, $values);
 
+    // Check write access on each property.
+    foreach (array_keys($values) as $name) {
+      if (!$this->propertyAccess($wrapper, $name, 'create')) {
+        services_error(t("Not authorized to set property '@p'", array('@p' => $name)), 403);
+      }
+    }
+
+    // Make sure that bundle information is present on entities that have
+    // bundles. We have to do this after creating the wrapper, because the
+    // name of the bundle key may differ from that of the corresponding
+    // metadata property (e.g. for taxonomy terms, the bundle key is
+    // 'vocabulary_machine_name', while the property is 'vocabulary').
+    if ($bundle_key = $wrapper->entityKey('bundle')) {
+      $entity = $wrapper->value();
+      if (empty($entity->{$bundle_key})) {
+        $entity_info = $wrapper->entityInfo();
+        if (isset($entity_info['bundles']) && count($entity_info['bundles']) === 1) {
+          // If the entity supports only a single bundle, then use that as a
+          // default. This allows creation of such entities if (as with ECK)
+          // they still use a bundle key.
+          $entity->{$bundle_key} = reset($entity_info['bundles']);
+        }
+        else {
+          services_error('Missing bundle: ' . $bundle_key, 406);
+        }
+      }
+    }
+
+    $properties = $wrapper->getPropertyInfo();
+    $diff = array_diff_key($values, $properties);
+    if (!empty($diff)) {
+      services_error('Unknown data properties: ' . implode(' ', array_keys($diff)) . '.', 406);
+    }
+    $entity = $wrapper->value();
+    // Manually adds UUID if required.
+    uuid_entity_presave($entity, $entity_type);
+    entity_uuid_save($entity_type, $entity);
+    return $this->retrieve($entity_type, $entity->{$uuid_key}, '*', NULL);
+  }
+
+  public function update($entity_type, $entity_uuid, array $values) {
+    $info = entity_get_info($entity_type);
+    $uuid_key = $info['entity keys']['uuid'];
+    $values[$uuid_key] = $entity_uuid;
+    $this->formSubmissionChecks('update', $entity_type, $values);
+    $ids = entity_get_id_by_uuid($entity_type, [$entity_uuid]);
+    $entity_id = reset($ids);
+    if (empty($entity_id)) {
+      // UUID is not existing, a PUT to create with specified UUID.
+      $values[$uuid_key] = $entity_uuid;
+      return $this->create($entity_type, $values);
+    }
+    if (!empty($values['author']['id'])) {
+      $uids = entity_get_id_by_uuid('user', [$values['author']['id']]); 
+      $values['author']['id'] = reset($uids);
+    }
+    $property_info = entity_get_all_property_info($entity_type);
+    $values = $this->transform_values($entity_type, $property_info, $values);
+    try {
+      $wrapper = entity_metadata_wrapper($entity_type, $entity_id);
+      foreach ($values as $name => $value) {
+        // Only attempt to set properties when the new value differs from that
+        // on the existing entity; otherwise, requests will fail for read-only
+        // and unauthorized properties, even if they are not being changed. This
+        // allows us to UPDATE a previously retrieved entity without removing
+        // such properties from the payload, as long as they are unchanged.
+        if (!$this->propertyHasValue($wrapper, $name, $value)) {
+          // We set the property before checking access so the new value
+          // will be passed to the access callback. This is necesssary in
+          // some cases (e.g. text-format fields) where access permissions
+          // depend on the value that is being set.
+          $wrapper->{$name}->set($value);
+          if (!$this->propertyAccess($wrapper, $name, 'update')) {
+            services_error(t("Not authorized to set property '@property-name'.", array('@property-name' => $name)), 403);
+          }
+        }
+      }
+    }
+    catch (EntityMetadataWrapperException $e) {
+      services_error($e->getMessage(), 406);
+    }
+    $entity = $wrapper->value();
+    entity_uuid_save($entity_type, $entity);
+    return $this->retrieve($entity_type, $entity->{$uuid_key}, '*', NULL);
   }
 
  /**
@@ -246,7 +363,7 @@ class RadarServicesEntityResourceController extends ServicesEntityResourceContro
           $data[$name] = $property->value();
         }
         elseif ($property instanceof EntityListWrapper) {
-          if (count($fields_array[$name])) {
+          if (isset($field_array[$name]) && count($fields_array[$name])) {
             $property_fields = implode(',', $fields_array[$name]);
           }
           else {
@@ -255,7 +372,7 @@ class RadarServicesEntityResourceController extends ServicesEntityResourceContro
           $data[$name] = $this->get_data($property, $property_fields);
         }
         elseif ($property instanceof EntityStructureWrapper) {
-          if (count($fields_array[$name])) {
+          if (isset($field_array[$name]) && count($fields_array[$name])) {
             $property_fields = implode(',', $fields_array[$name]);
           }
           else {
@@ -335,12 +452,52 @@ class RadarServicesEntityResourceController extends ServicesEntityResourceContro
    * @return type
    */
   protected function transform_values($entity_type, $property_info, $values) {
-    foreach($values as $key => $value) {
+    foreach ($values as $key => $value) {
+      // Stop gap while we still have a field_type.
+      if ($key == 'type') {
+        continue;
+      }
       // Handle Resource references so we can pass pack the object.
       if (is_array($value) && isset($value['id'])) {
         $values[$key] = $value['id'];
       }
-      // Check if this is actually a field_ value
+      // Also if it's a multiple field.
+      if (is_array($value)) {
+        foreach ($value as $delta => $item) {
+          // Handle Resource references so we can pass pack the object.
+          if (is_array($item) && isset($item['id'])) {
+            $values[$key][$delta] = $item['id'];
+          }
+          // Reformat incoming date.
+          if (is_array($item) && isset($item['time_start'])) {
+            if (empty($item['timezone'])) {
+              services_error(t('Timezone required: @key', ['@key' => $key]), 400);
+            }
+            $date = $item['time_start'];
+            $date_match = [];
+            if (preg_match('/^(\d{4}-\d{2}-\d{2}).(\d{2}:\d{2}:\d{2})/', $date, $date_match)) {
+              $values[$key][$delta]['value'] = $date_match[1] . ' ' . $date_match[2];
+            }
+            else {
+              services_error(t('Date @key time_start required format YYYY-MM-DD HH:MM:SS', ['@key' => $key]), 400);
+            }
+            if (!empty($item['time_end'])) {
+              $date = $item['time_end'];
+              $date_match = [];
+              if (preg_match('/^(\d{4}-\d{2}-\d{2}).(\d{2}:\d{2}:\d{2})/', $date, $date_match)) {
+                $values[$key][$delta]['value2'] = $date_match[1] . ' ' . $date_match[2];
+              }
+              else {
+                services_error(t('Date @key time_end required format YYYY-MM-DD HH:MM:SS', ['@key' => $key]), 400);
+              }
+            }
+            else {
+              $values[$key][$delta]['value2'] = $date_match[1] . ' ' . $date_match[2];
+            }
+          }
+        }
+      }
+      // Check if this is actually a field_ value.
       if (isset($property_info['field_' . $key])) {
         $values['field_' . $key] = $values[$key];
         unset($values[$key]);
@@ -400,4 +557,157 @@ class RadarServicesEntityResourceController extends ServicesEntityResourceContro
     return $value == $property->value();
   }
 
+  // Validation.
+  //
+  // Various permissions assume access via the form. These need to recreate
+  // this:
+  //  * groups are unpublished.
+  //  * users without moderation permissions can't change owner, status, date on
+  //  node.
+  //  * og permissions are checked for groups and proposed groups.
+  private function formSubmissionChecks($op, $entity_type, &$values) {
+    global $user;
+
+    if ($op == 'update') {
+      if (!user_access('administer nodes')) {
+        unset($values['uid'], $values['author'], $values['name'], $values['created'], $values['revision']);
+      }
+      if (isset($values['log'])) {
+        $values['log'] .= "\nVia API.\n";
+      }
+      else {
+        $values['log'] = 'Via API';
+      }
+
+      if (isset($values['og_group_ref']) || isset($values['og_group_requst'])) {
+        $info = entity_get_info($entity_type);
+        $uuid_key = $info['entity keys']['uuid'];
+        $entity_ids = entity_get_id_by_uuid($entity_type, [$values[$uuid_key]]);
+        $entity_id = reset($entity_ids);
+        $original = entity_metadata_wrapper($entity_type, $entity_id);
+        $this->filterUpdatePostingGroups($values['og_group_ref'], $values['og_group_request'], $entity_type, $values['type'], $original);
+        if (empty($values['og_group_ref'])) {
+          $values['status'] = 0;
+        }
+      }
+    }
+    if ($op == 'create') {
+      $values['author'] = ['id' => $user->uuid];
+      $values['created'] = time();
+      if (isset($values['log'])) {
+        $values['log'] .= "\nVia API.\n";
+      }
+      else {
+        $values['log'] = 'Via API';
+      }
+      if (!user_access('administer nodes')) {
+        if ($entity_type == 'node') {
+          if ($values['type'] == 'event') {
+            $this->filterCreatePostingGroups($values['og_group_ref'], $values['og_group_request'], $values['type']);
+            if (empty($values['og_group_ref'])) {
+              $values['status'] = 0;
+            }
+          }
+          else {
+            $values['status'] = 0;
+          }
+        }
+      }
+    }
+  }
+
+  private function filterCreatePostingGroups(&$og_group_ref, &$og_group_request, $type) {
+    $post = [];
+    $request = [];
+
+    foreach ((array) $og_group_ref as $group) {
+      $gids = entity_get_id_by_uuid('node', [$group['id']]);
+      $gid = reset($gids);
+      if (og_user_access('node', $gid, "create $type content")) {
+        $post[] = $group;
+      }
+      else {
+        // Push to see if it can be a request.
+        $og_group_request[] = $group;
+      }
+    }
+
+    foreach ((array) $og_group_request as $group) {
+      $gids = entity_get_id_by_uuid('node', [$group['id']]);
+      $gid = reset($gids);
+      if (radar_og_can_post_request($gid, $type)) {
+        $request[] = $group;
+      }
+    }
+
+    $og_group_ref = $post;
+    $og_group_request = $request;
+  }
+
+  private function filterUpdatePostingGroups(&$og_group_ref, &$og_group_request, $entity_type, $type, $original) {
+    global $user;
+    $uid = $user->uid;
+
+    $post = [];
+    foreach ((array) $og_group_ref as $group) {
+      $nids = entity_get_id_by_uuid($entity_type, [$group['id']]);
+      $post[] = reset($nids);
+    }
+    print_r($post);
+    $request = [];
+    foreach ((array) $og_group_request as $group) {
+      $nids = entity_get_id_by_uuid($entity_type, [$group['id']]);
+      $request[] = reset($nids);
+    }
+
+    $new_groups = array_diff($post, $original->og_group_ref->raw());
+    $del_groups = array_diff($original->og_group_ref->raw(), $post);
+    $new_request = array_diff($request, $original->og_group_request->raw());
+    $del_request = array_diff($original->og_group_request->raw(), $request);
+
+    foreach ($new_groups as $gid) {
+      if (!og_user_access('node', $gid, "create $type content")) {
+        $uuids = entity_get_uuid_by_id('node', [$gid]);
+        $uuid = reset($uuids);
+        foreach ($og_group_ref as $key => $ref) {
+          if ($ref['id'] == $uuid) {
+            unset($og_group_ref[$key]);
+          }
+        }
+      }
+    }
+
+    if ($original->author->uid != $uid) {
+      foreach ($del_groups as $gid) {
+        if (!og_user_access('node', $gid, "create $type content")) {
+          $uuids = entity_get_uuid_by_id('node', [$gid]);
+          $uuid = reset($uuids);
+          $og_group_ref[] = ['id' => $uuid];
+        }
+      }
+    }
+
+    foreach ($new_request as $gid) {
+      if (!radar_og_can_post_request($gid, $type)) {
+        $uuids = entity_get_uuid_by_id('node', [$gid]);
+        $uuid = reset($uuids);
+        foreach ($og_group_request as $key => $ref) {
+          if ($ref['id'] == $uuid) {
+            unset($og_group_request[$key]);
+          }
+        }
+      }
+    }
+
+    if ($original->author->uid != $uid) {
+      foreach ($del_request as $gid) {
+        if (!og_user_access('node', $gid, "create $type content")) {
+          $uuids = entity_get_uuid_by_id('node', [$gid]);
+          $uuid = reset($uuids);
+          $og_group_ref[] = ['id' => $uuid];
+        }
+      }
+    }
+  }
+
 }
diff --git a/modules/radar/radar_services/radar_services.features.user_permission.inc b/modules/radar/radar_services/radar_services.features.user_permission.inc
index cc3dd56fb9abcf6ab74907b485181919f0ae6297..7174b9f8063b274cfab2c1c2fc1080fe9a996955 100644
--- a/modules/radar/radar_services/radar_services.features.user_permission.inc
+++ b/modules/radar/radar_services/radar_services.features.user_permission.inc
@@ -10,6 +10,15 @@
 function radar_services_user_default_permissions() {
   $permissions = array();
 
+  // Exported permission: 'services create update delete operations'.
+  $permissions['services create update delete operations'] = array(
+    'name' => 'services create update delete operations',
+    'roles' => array(
+      'authenticated user' => 'authenticated user',
+    ),
+    'module' => 'radar_services',
+  );
+
   // Exported permission: 'services_search_api search from any index'.
   $permissions['services_search_api search from any index'] = array(
     'name' => 'services_search_api search from any index',
diff --git a/modules/radar/radar_services/radar_services.info b/modules/radar/radar_services/radar_services.info
index 79fb36a5be0804244c810cd477c80337f04d60ce..fffbadfdad98f45d31d62ca604c3a0cdea7b77e4 100644
--- a/modules/radar/radar_services/radar_services.info
+++ b/modules/radar/radar_services/radar_services.info
@@ -7,6 +7,7 @@ project = radar_services
 dependencies[] = cors
 dependencies[] = ctools
 dependencies[] = features
+dependencies[] = radar_services
 dependencies[] = rest_server
 dependencies[] = services
 dependencies[] = services_search_api
@@ -17,6 +18,7 @@ features[features_api][] = api:2
 features[services_endpoint][] = api_1_0
 features[services_endpoint][] = api_1_1
 features[services_endpoint][] = api_1_2
+features[user_permission][] = services create update delete operations
 features[user_permission][] = services_search_api search from any index
 features[variable][] = cors_domains
 features[variable][] = services_entity_resource_class
diff --git a/modules/radar/radar_services/radar_services.module b/modules/radar/radar_services/radar_services.module
index 31e48310ce0c91433c87caa3cb61ae2c35aa4acd..9c3a239fb3e0904fa215ca4acf5b71c37e07bf63 100644
--- a/modules/radar/radar_services/radar_services.module
+++ b/modules/radar/radar_services/radar_services.module
@@ -7,6 +7,18 @@
 include_once 'radar_services.features.inc';
 
 
+/**
+ * Implements hook_permission().
+ */
+function radar_services_permission() {
+  return [
+    'services create update delete operations' => [
+      'title' => t('Perform create, update and delete operations with services'),
+      'description' => t('Additional permission to perform operations that could be done by the user via the website ui.'),
+    ],
+  ];
+}
+
 /**
  * Implements hook_services_resources().
  */