diff --git a/src/server/meta/DapiMetaSource.js b/src/server/meta/DapiMetaSource.js
new file mode 100644
index 0000000..fecfe2d
--- /dev/null
+++ b/src/server/meta/DapiMetaSource.js
@@ -0,0 +1,109 @@
+/**
+ * Data-API-based metadata population
+ *
+ * Copyright (C) 2017 R-T Specialty, LLC.
+ *
+ * This file is part of the Liza Data Collection Framework.
+ *
+ * liza is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+"use strict";
+
+const { Class } = require( 'easejs' );
+
+
+/**
+ * Retrieve data for meta field using Data API
+ *
+ * TODO: The reason this class exists at all is to encapsulate the horrid
+ * API. Once refactored, perhaps this class will no longer be necessary.
+ */
+module.exports = Class( 'DapiMetaSource',
+{
+ /**
+ * Metabucket constructor
+ * @type {function()}
+ */
+ 'private _bucketf': null,
+
+
+ /**
+ * Initialize with metabucket constructor
+ * @type {function()}
+ */
+ constructor( bucketf )
+ {
+ this._bucketf = bucketf;
+ },
+
+
+ /**
+ * Retrieve field data
+ *
+ * @param {string} field field name
+ * @param {number} index field index
+ * @param {DataApiManager} dapi_manager manager for dapi calls
+ * @param {Object} dapi dapi descriptor
+ * @param {Object} data dapi input data
+ *
+ * @return {Promise} object containing `field`, `index`, and return data
+ */
+ 'public getFieldData'( field, index, dapi_manager, dapi, data )
+ {
+ const metabucket = this._bucketf();
+
+ return new Promise( ( resolve, reject ) =>
+ {
+ dapi_manager.getApiData(
+ dapi.name,
+ data,
+ ( err, api_data ) =>
+ {
+ dapi_manager.setFieldData(
+ dapi.name,
+ index,
+ api_data,
+ dapi.value,
+ '',
+ false
+ );
+
+ dapi_manager.expandFieldData(
+ dapi.name,
+ index,
+ metabucket,
+ dapi.mapdest,
+ true,
+ {
+ [dapi.name]: {
+ [index]: api_data[ 0 ][ dapi.value ],
+ },
+ }
+ );
+
+ resolve( {
+ field: field,
+ index: index,
+ data: metabucket.getData(),
+ } );
+ },
+ field,
+ index,
+ {},
+ reject
+ );
+ } );
+ },
+} );
diff --git a/test/server/meta/DapiMetaSourceTest.js b/test/server/meta/DapiMetaSourceTest.js
new file mode 100644
index 0000000..87ff860
--- /dev/null
+++ b/test/server/meta/DapiMetaSourceTest.js
@@ -0,0 +1,149 @@
+/**
+ * Tests Data-API-based metadata population
+ *
+ * Copyright (C) 2017 R-T Specialty, LLC.
+ *
+ * This file is part of the Liza Data Collection Framework.
+ *
+ * liza is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+"use strict";
+
+const expect = require( 'chai' ).expect;
+const Sut = require( '../../../' ).server.meta.DapiMetaSource;
+
+describe( "DapiMetaSource", () =>
+{
+ it( "populates field with dapi response", () =>
+ {
+ const dapim = createStubDapiManager();
+ const field_name = 'field_foo';
+ const index = 1;
+
+ const dapi = {
+ name: 'dapi_name',
+ value: 'foo',
+ mapdest: { map: 'dest' },
+ };
+
+ // input data to dapi
+ const given_data = {};
+
+ // dapi output data (response)
+ const ret_data = [ {} ];
+
+ const bucket_result = {
+ [dapi.name]: {
+ [index]: ret_data[ 0 ][ dapi.value ],
+ },
+ };
+
+ const metabucket = getStubBucket();
+
+ // g prefix = "given"
+ // all these show why we want to encapsulate this garbage
+ dapim.getApiData = ( gapi, gdata, gcallback, gname, gindex ) =>
+ {
+ expect( gapi ).to.equal( dapi.name );
+ expect( gdata ).to.equal( given_data );
+ expect( gname ).to.equal( field_name );
+ expect( gindex ).to.equal( index );
+
+ // make sure we handle async
+ process.nextTick( () => gcallback( null, ret_data ) );
+ };
+
+ dapim.setFieldData =
+ ( gname, gindex, gdata, gvalue, glabel, gunchanged ) =>
+ {
+ expect( gname ).to.equal( dapi.name );
+ expect( gindex ).to.equal( index );
+ expect( gdata ).to.equal( ret_data );
+ expect( gvalue ).to.equal( dapi.value );
+ expect( glabel ).to.equal( '' ); // unused
+ expect( gunchanged ).to.equal( false );
+ };
+
+ dapim.expandFieldData =
+ ( gname, gindex, gbucket, gmap, gpredictive, gdiff ) =>
+ {
+ expect( gname ).to.equal( dapi.name );
+ expect( gindex ).to.equal( index );
+ expect( gbucket ).to.equal( metabucket );
+ expect( gmap ).to.equal( dapi.mapdest );
+ expect( gpredictive ).to.equal( true );
+ expect( gdiff ).to.deep.equal( bucket_result );
+
+ metabucket.getData = () => bucket_result;
+ };
+
+ return Sut( () => metabucket )
+ .getFieldData( field_name, index, dapim, dapi, given_data )
+ .then( result =>
+ {
+ expect( result.field ).to.equal( field_name );
+ expect( result.index ).to.equal( index );
+ expect( result.data ).to.equal( bucket_result );
+ } );
+ } );
+
+
+ it( "rejects promise on error", () =>
+ {
+ const e = Error( "Test error" );
+ const dapim = createStubDapiManager();
+
+ dapim.getApiData = ( _, __, ___, ____, _____, ______, failc ) =>
+ {
+ failc( e );
+ };
+
+ return Sut( () => getStubBucket() )
+ .getFieldData( 'name', 0, dapim, {}, {} )
+ .catch( given_e =>
+ {
+ expect( given_e ).to.equal( e );
+
+ return true;
+ } );
+ } );
+} );
+
+
+function createStubDapiManager()
+{
+ return {
+ getApiData() {},
+ setFieldData() {},
+ expandFieldData() {},
+ };
+}
+
+
+function getStubBucket()
+{
+ return {
+ setValues() {},
+ getData() {},
+ };
+}
+
+
+function createStubDb()
+{
+ return {
+ saveQuoteMeta() {},
+ };
+}