Le API di search rispondono alla url "{sitecollection}/_api/search/query" ed è possibile interrogarle sia in GET, passando i parametri in querystring, oppure in POST passando un oggetto json nel body.

Ad esempio per cercare la parola "sharepoint" con il metodo GET, la url diventa:
https://sharepoint.local/search/_api/search/query?querytext='sharepoint'
Ho aggiunto nel path della url la site collection /search/, in questo modo anche chi non ha accesso alla site collection di root può interrogare il search per cercare sulle altre site collection a cui ha accesso.

L'esempio seguente, per SharePoint Framework (SPFx), utilizza il metodo GET per farsi ritornare tutte le site collection (parametro "Path:" e "contentclass:STS_Site") la cui url inizia con startingUrl;
public getSitesStartingWith(startingUrl: string): Promise<ILinkItem[]> {
  const url = `/search/_api/search/query?querytext='Path:${startingUrl}* AND contentclass:STS_Site'&selectproperties='Title,Path'&trimduplicates=false&rowLimit=500`;
  const items: ILinkItem[] = [];

  return this._spHttpClient.get(url, SPHttpClient.configurations.v1)
    .then((response: SPHttpClientResponse) => {
      return response.json().then((data) => {
        if (data["odata.error"]) {
          //se ho degli errori ritorno l'array vuoto
          Log.error(Constants.LOG_SOURCE, new Error(data["odata.error"].message.value));
          return items;
        }
        const rows = data.PrimaryQueryResult.RelevantResults.Table.Rows;
        rows.forEach(c => {
          const objTitle = c.Cells.filter((cell) => { return cell.Key == "Title"; })[0];
          const objUrl = c.Cells.filter((cell) => { return cell.Key == "Path"; })[0];

          // ritorno solo il titolo e la url
          items.push({
            title: objTitle.Value,
            url: objUrl.Value
          });
        });

        //custom sort: &sortlist='Title:ascending' -> non funziona da errore
        items.sort((a, b) => {
          if (a.title < b.title)
            return -1;
          if (a.title > b.title)
            return 1;
          return 0;
        });

        return items;
      });
    });
}
L'esempio è limitato a 500 items ritornati, se il numero degli elementi ritornati è superiore a 500 sarà necessario eseguire più query paginando i dati.
Per eseguire l'ordinamento è disponibile anche il parametro sortlist, ma non sono riuscito a farlo funzionare sul campo Title, dava sempre errore.
Visto che nel mio caso i dati ritornati erano sicuramente minori di 500 ho utilizzato il metodo sort degli array

Questo è lo stesso esempio ma con il metodo POST (/_api/search/postquery), utile nel caso in cui i parametri di ricerca superano la lunghezza massima della url:
public postSitesStartingWith(startingUrl: string, siteUrls: string[] | null): Promise<ILinkItem[]> {
  const url = '/search/_api/search/postquery';
  const items: ILinkItem[] = [];

  let queryText =  `Path:${startingUrl}* AND contentclass:STS_Site`;
  const requestBody = {
    'request': {
      '__metadata': { 'type': 'Microsoft.Office.Server.Search.REST.SearchRequest' },
      'Querytext': queryText,
      'RowLimit': 500,
      'SelectProperties': {
        'results': ['Title', 'Path']
      },
      'TrimDuplicates': 'false',
      'ProcessPersonalFavorites': 'false',
      //'EnableSorting' : 'false',
      //'SortList': { 'results': [{ 'Property': 'Title', 'Direction': '0' }] }  // da errore 
    }
  };

  const options: ISPHttpClientOptions = {
    headers: { "odata-version": "3.0" }, //senza questo da errore: "Unknown Error"
    body: JSON.stringify(requestBody)
  };

  return this._spHttpClient.post(url, SPHttpClient.configurations.v1, options)
    .then((response: SPHttpClientResponse) => {
      return response.json().then((data) => {
        if (data["odata.error"]) {
          Log.error(Constants.LOG_SOURCE, new Error(data["odata.error"].message.value));
          return items;
        }
        const rows = data.PrimaryQueryResult.RelevantResults.Table.Rows;
        rows.forEach(c => {

          const objTitle = c.Cells.filter((cell) => { return cell.Key == "Title"; })[0];
          const objUrl = c.Cells.filter((cell) => { return cell.Key == "Path"; })[0];

          items.push({
            title: objTitle.Value,
            url: objUrl.Value
          });
        });

        //custom sort: &sortlist='Title:ascending' -> non funziona da errore
        items.sort((a, b) => {
          if (a.title < b.title)
            return -1;
          if (a.title > b.title)
            return 1;
          return 0;
        });

        return items;
      });
    });
}

Per eseguire l'esempio è necessario costruire una classe come questa:
import { Log } from '@microsoft/sp-core-library';
import { SPHttpClient, SPHttpClientResponse, ISPHttpClientOptions } from '@microsoft/sp-http';
import { WebPartContext } from '@microsoft/sp-webpart-base';

export interface ILinkItem {
  title: string;
  url: string;
}

export default class SPDataService {
  private _spHttpClient: SPHttpClient;
  private _webAbsoluteUrl: string;

  constructor(private context: WebPartContext) {
    this._spHttpClient = context.spHttpClient;
    this._webAbsoluteUrl = context.pageContext.web.absoluteUrl;
  }

  public getSitesStartingWith(startingUrl: string): Promise<ILinkItem[]> { ... }

  public postSitesStartingWith(startingUrl: string): Promise<ILinkItem[]> { ... }
}
da richiamare nella web part con:
//private _dataService: IDataService;
...
this._dataService = DataService.get(this.context);
...
//cerco tutte le site collection che iniziano per "c"
this._dataService.postSitesStartingWith("http://sharepoint.local/sites/c")
  .then((items) => { ... })
  .catch((error) => { ... });

Vedi anche è Keyword queries and search conditions for Content Search e Keyword Query Language (KQL) syntax reference