diff --git a/data-discovery/src/main/java/vlkb/output/XmlSerializer.java b/data-discovery/src/main/java/vlkb/output/XmlSerializer.java
index ee3c2cde5ba5c28f7ec69bf39bef700dcc1f15fc..5bda5b38bf96d3e1d0a3ff002abd49d25b1e2c6d 100644
--- a/data-discovery/src/main/java/vlkb/output/XmlSerializer.java
+++ b/data-discovery/src/main/java/vlkb/output/XmlSerializer.java
@@ -9,6 +9,8 @@ import java.io.IOException;
 import java.io.OutputStreamWriter;
 import java.io.BufferedWriter;
 
+import java.util.List;
+import java.util.ArrayList;
 
 
 final class XmlSerializer
@@ -135,6 +137,144 @@ final class XmlSerializer
    }
 
 
+
+
+   public static void serializeToVoTableBySurveys(
+         PrintWriter writer, String charEncoding,
+         SearchOutputData searchOutputData,
+         boolean showDuration, long startTime_msec) throws IOException
+   {
+      BufferedWriter out = new BufferedWriter( writer );
+
+      out.write( "<VOTABLE version='1.1'>" );
+      out.write( "<DESCRIPTION> " + searchOutputData.versionString + 
+             " SUBSURVCOUNT: " + searchOutputData.subsurveyArr.length + " </DESCRIPTION>" );
+
+      if((searchOutputData.subsurveyArr != null) && (searchOutputData.subsurveyArr.length > 0))
+      {
+         // assumes ORDERED subsurveyArray: by surveyname
+
+         List<StarTable> tableList = new ArrayList();
+         String prevSurveyname = searchOutputData.subsurveyArr[0].surveyname.trim();
+         for(Subsurvey subsurvey : searchOutputData.subsurveyArr)
+         {
+
+            if(prevSurveyname.equals(subsurvey.surveyname.trim()))
+            {
+               StarTable table = makeSubsurveyTable( subsurvey );
+               tableList.add(table);
+            }
+            else
+            {
+               out.write( "<RESOURCE>" );
+               out.write( "<DESCRIPTION> " + prevSurveyname + " </DESCRIPTION>" );
+
+               /* PLACEHOLDER FOR RESOURCE PARAM
+
+                  table.setParameter(new DescribedValue(
+                  new DefaultValueInfo("subsurveyCount",Integer.class,
+                  "Count of subsurveys with found datacube(s)" ),
+                  subsurveyArr.length ) );
+
+                  table.setParameter(new DescribedValue(
+                  new DefaultValueInfo( "survey",  String.class,
+                  "Survey name" ),
+                  subsurvey.surveyname ) );
+
+*/
+
+               StarTable[] tables = tableList.toArray(new StarTable[0]);
+               for ( int i = 0; i < tables.length; i++ )
+               {
+                  VOSerializer.makeSerializer( DataFormat.TABLEDATA, tables[i] ).writeInlineTableElement( out );
+               }
+               tableList.clear();
+
+               out.write( "</RESOURCE>" );
+
+               prevSurveyname = subsurvey.surveyname.trim();
+               StarTable table = makeSubsurveyTable( subsurvey );
+               tableList.add(table);
+            }
+
+         }
+
+         if(!tableList.isEmpty())
+         {
+            out.write( "<RESOURCE>" );
+            out.write( "<DESCRIPTION> " + prevSurveyname + " </DESCRIPTION>" );
+
+            /* PLACEHOLDER FOR RESOURCE PARAM
+
+               table.setParameter(new DescribedValue(
+               new DefaultValueInfo("subsurveyCount",Integer.class,
+               "Count of subsurveys with found datacube(s)" ),
+               subsurveyArr.length ) );
+
+               table.setParameter(new DescribedValue(
+               new DefaultValueInfo( "survey",  String.class,
+               "Survey name" ),
+               subsurvey.surveyname ) );
+
+*/
+
+            StarTable[] tables = tableList.toArray(new StarTable[0]);
+            for ( int i = 0; i < tables.length; i++ )
+            {
+               VOSerializer.makeSerializer( DataFormat.TABLEDATA, tables[i] ).writeInlineTableElement( out );
+            }
+            tableList.clear();
+
+            out.write( "</RESOURCE>" );
+         }
+
+      }
+
+      out.write( "</VOTABLE>" );
+      out.flush();
+   }
+
+
+
+   private static StarTable makeSubsurveyTable(Subsurvey subsurvey)
+   {
+      RowListStarTable table = new RowListStarTable( ObscoreExt.OBSCORE_VLKB_COLINFO );
+
+      table.setParameter(new DescribedValue(
+               new DefaultValueInfo("datacubeCount", Integer.class, "Count of all datacubes from VLKB-search" ),
+               subsurvey.datasetArr.length ) );
+
+      table.setParameter(new DescribedValue(
+               new DefaultValueInfo( "velocity_unit",  String.class, "Unit of velocity in FITS header" ),
+               subsurvey.vel_unit ) );
+
+      table.setParameter(new DescribedValue(
+               new DefaultValueInfo( "survey",  String.class, "Survey name" ),
+               subsurvey.surveyname ) );
+
+      table.setParameter(new DescribedValue(
+               new DefaultValueInfo( "species",  String.class, "Species" ),
+               subsurvey.species ) );
+
+      table.setParameter(new DescribedValue(
+               new DefaultValueInfo( "transition",  String.class, "Transition" ),
+               subsurvey.transition ) );
+
+      table.setParameter(new DescribedValue(
+               new DefaultValueInfo( "description",  String.class, "Reference description" ),
+               subsurvey.description ) );
+
+      for(Dataset dataset : subsurvey.datasetArr)
+      {
+         if(dataset.obsCore == null) continue; // FIXME skip mergeable datasets
+
+         table.addRow( ObscoreExt.obscoreVlkbRow(dataset) );
+      }
+
+      return table;
+   }
+
+
    // legacy
 
    public static void serializeToLegacyResults(
diff --git a/data-discovery/src/main/java/vlkb/webapi/FormatResponseFilter.java b/data-discovery/src/main/java/vlkb/webapi/FormatResponseFilter.java
index 6a9121615fc75a3cdcc3d55844d685f4f4bfb068..89912a0adb551054238710d08c29aa1f07944082 100644
--- a/data-discovery/src/main/java/vlkb/webapi/FormatResponseFilter.java
+++ b/data-discovery/src/main/java/vlkb/webapi/FormatResponseFilter.java
@@ -156,9 +156,15 @@ public class FormatResponseFilter implements Filter
             boolean showDuration = false;
 
             if(respFormat.contains("mode=bysubsurveys"))
-               XmlSerializer.serializeToVoTableBySubsurveys(responseWriter, RESPONSE_ENCODING, searchOutputData,showDuration,startTime_msec);
+               XmlSerializer.serializeToVoTableBySubsurveys(responseWriter, RESPONSE_ENCODING,
+                     searchOutputData,showDuration,startTime_msec);
+            else if(respFormat.contains("mode=bysurveys"))
+               XmlSerializer.serializeToVoTableBySurveys(responseWriter, RESPONSE_ENCODING,
+                     searchOutputData,showDuration,startTime_msec);
             else
-               XmlSerializer.serializeToVoTable(responseWriter, RESPONSE_ENCODING, searchOutputData,showDuration,startTime_msec);
+               XmlSerializer.serializeToVoTable(responseWriter, RESPONSE_ENCODING,
+                     searchOutputData,showDuration,startTime_msec);
+
          }
          else
          {