Finally I have discovered how to do this satisfactorily.

My requirement is to be able to create some tags - for example when installing xbCulture Components or when converting xbRefs Text References to Tag References in xbRefMan. These tags need to have a description and parent set as well as just a title so the simple function in TagsHelper::createTagsFromField(array()) does not cut the mustard. It only lets you create simple tags with just a title, and has a weird syntax whereby the title string has to be prefixed with #new# 

 

The answer is to get a tags table object and use the bind() , check() and store() table functions. In addition to get the parent_id set correctly you have to call the setLocation() function before store() - this will set up the lft, rgt, and level fields but not the path field. To do that you have to rebuildPath($id) with the new tag id after store().

So putting it all together with some error checking and detecting if a tag with the same alias already exists in a Component Helper function to which I can pass the data for the new tag as an array I get this:

 public static function createTag(array $tagdata) {
     Table::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_tags/tables');
     $app = Factory::getApplication(); 
     //check if alias already exists (dupe id)
     $alias = $tagdata['title'];
     $alias = ApplicationHelper::stringURLSafe($alias); 
     if (trim(str_replace('-', '', $alias)) == '') {
         $alias = Factory::getDate()->format('Y-m-d-H-i-s');
     }
     $db = Factory::getDBO();
     $query = $db->getQuery(true);
     $query->select('id')->from($db->quoteName('#__tags'))->where($db->quoteName('alias').' = '.$db->quote($alias));
     $db->setQuery($query);
     $id = $db->loadResult();
     if ($id>0) {
          $app->enqueueMessage('Tag with alias '.$alias.' already exists with id '.$id,'Warning');
          return $id;
     }
 
     $table = Table::getInstance('Tag', 'TagsTable', array()); 
     // Bind data
     if (!$table->bind($tagdata)) {
         $app->enqueueMessage($table->getError(),'Error');
         return false;
     }
     // Check the data.
     if (!$table->check()) {
         $app->enqueueMessage($table->getError(),'Error');
         return false;
     }
     // set the parent details
     $table->setLocation($tagdata['parent_id'], 'last-child'); //no error reporting from this one
     // Store the data.
     if (!$table->store()){
         $app->enqueueMessage($table->getError(),'Error');
         return false;
     }
     if (!$table->rebuildPath($table->id)) {
         $app->enqueueMessage($table->getError(),'Error');
         return false;
     }
     $app->enqueueMessage('New tag '.$tagdata['title'].' created with id '.$table->id);
     return $table->id; 
 }

The data array is an assoc array and must include 'title', 'parent_id' (=1 if no parent) and 'published' (=1 for published as usual). 'description' is optional, as are all the other fields like publilication data will get set automatically if needed.

It seems to work ok.