diff --git a/src/bucket/Bucket.js b/src/bucket/Bucket.js
new file mode 100644
index 0000000..5db0799
--- /dev/null
+++ b/src/bucket/Bucket.js
@@ -0,0 +1,117 @@
+/**
+ * Generalized key-value store
+ *
+ * Copyright (C) 2016 LoVullo Associates, Inc.
+ *
+ * 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 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 .
+ *
+ * There's a hole in my bucket, dear Liza, dear Liza [...]
+ */
+
+var Interface = require( 'easejs' ).Interface;
+
+
+/**
+ * Represents an object that is able to store key-value data with multiple
+ * indexes per value
+ */
+module.exports = Interface( 'Bucket',
+{
+ /**
+ * Explicitly sets the contents of the bucket
+ *
+ * @param {Object.} data associative array of the data
+ *
+ * @param {boolean} merge_index whether to merge indexes individually
+ * @param {boolean} merge_null whether to merge undefined values (vs
+ * ignore)
+ *
+ * @return {Bucket} self
+ */
+ 'public setValues': [ 'data', 'merge_index', 'merge_null' ],
+
+
+ /**
+ * Overwrites values in the original bucket
+ *
+ * @param {Object.} data associative array of the data
+ *
+ * @return {Bucket} self
+ */
+ 'public overwriteValues': [ 'data' ],
+
+
+ /**
+ * Clears all data from the bucket
+ *
+ * @return {Bucket} self
+ */
+ 'public clear': [],
+
+
+ /**
+ * Calls a function for each each of the values in the bucket
+ *
+ * Note: This format is intended to be consistent with Array.forEach()
+ *
+ * @param {function( Object, number )} callback function to call for each
+ * value in the bucket
+ *
+ * @return {Bucket} self
+ */
+ 'public each': [ 'callback' ],
+
+
+ /**
+ * Returns the data for the requested field
+ *
+ * @param {string} name field name (with or without trailing brackets)
+ *
+ * @return {Array} data for the field, or empty array if none
+ */
+ 'public getDataByName': [ 'name' ],
+
+
+ /**
+ * Returns the data as a JSON string
+ *
+ * @return {string} data represented as JSON
+ */
+ 'public getDataJson': [],
+
+
+ /**
+ * Return raw bucket data
+ *
+ * TODO: remove; breaks encapsulation
+ *
+ * @return {Object} raw bucket data
+ */
+ 'public getData': [],
+
+
+ /**
+ * Calls a function for each each of the values in the bucket matching the
+ * given predicate
+ *
+ * @param {function(string)} pred predicate
+ * @param {function( Object, number )} callback function to call for each
+ * value in the bucket
+ *
+ * @return {Bucket} self
+ */
+ 'public filter': [ 'pred', 'callback' ]
+} );
diff --git a/src/dapi/BucketDataApi.js b/src/dapi/BucketDataApi.js
new file mode 100644
index 0000000..b54d6df
--- /dev/null
+++ b/src/dapi/BucketDataApi.js
@@ -0,0 +1,90 @@
+/**
+ * Retrieves API data from bucket
+ *
+ * Copyright (C) 2016 LoVullo Associates, Inc.
+ *
+ * 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 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 .
+ */
+
+var Class = require( 'easejs' ).Class,
+ DataApi = require( './DataApi' ),
+ Bucket = require( '../bucket/Bucket' );
+
+
+/**
+ * Retrieve data from the bucket
+ */
+module.exports = Class( 'BucketDataApi' )
+ .implement( DataApi )
+ .extend(
+{
+ /**
+ * Bucket to use as data source
+ * @type {Bucket}
+ */
+ 'private _bucket': null,
+
+ 'private _params': {},
+
+
+ /**
+ * Initialize data API
+ *
+ * @param {string} url service URL
+ * @param {RestDataApiStrategy} strategy request strategy
+ */
+ __construct: function( bucket, params )
+ {
+ if ( !( Class.isA( Bucket, bucket ) ) )
+ {
+ throw Error( "Invalid bucket provided" );
+ }
+
+ this._bucket = bucket;
+ this._params = params;
+ },
+
+
+ /**
+ * Request data from the bucket
+ *
+ * @param {Object} data request params
+ * @param {function(Object)} callback server response callback
+ *
+ * @return {BucketDataApi} self
+ */
+ 'public request': function( data, callback )
+ {
+ var _self = this.__inst,
+ rows = [];
+
+ for ( var i in this._params )
+ {
+ var field = this._params[ i ],
+ fdata = this._bucket.getDataByName( field );
+
+ for ( var index in fdata )
+ {
+ rows[ index ] = rows[ index ] || {};
+ rows[ index ][ field ] = fdata[ index ];
+ }
+ }
+
+ callback( null, rows );
+
+ return this;
+ }
+} );
diff --git a/src/dapi/DataApiManager.js b/src/dapi/DataApiManager.js
new file mode 100644
index 0000000..0f2e469
--- /dev/null
+++ b/src/dapi/DataApiManager.js
@@ -0,0 +1,683 @@
+/**
+ * Manages DataAPI requests and return data
+ *
+ * Copyright (C) 2016 LoVullo Associates, Inc.
+ *
+ * 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 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 .
+ */
+
+var Class = require( 'easejs' ).Class,
+ EventEmitter = require( 'events' ).EventEmitter;
+
+
+/**
+ * Pends and manages API calls and return data
+ *
+ * TODO: Extracted pretty much verbatim from Program; needs refactoring
+ */
+module.exports = Class( 'DataApiManager' )
+ .extend( EventEmitter,
+{
+ /**
+ * Factory used to create data APIs
+ * @type {DataApiFactory}
+ */
+ 'private _dataApiFactory': null,
+
+ /**
+ * DataApi instances, indexed by API id
+ * @type {Object}
+ */
+ 'private _dataApis': {},
+
+ /**
+ * Data returned for fields via a data API, per index, formatted
+ * @type {Object}
+ */
+ 'private _fieldData': {},
+
+ /**
+ * Data returned for fields via a data API, per index, unformatted
+ * @type {Object}
+ */
+ 'private _fieldRawData': {},
+
+ /**
+ * Pending API calls (by tracking identifier, not API id)
+ * @type {Object}
+ */
+ 'private _pendingApiCall': {},
+
+ /**
+ * API calls queued for request
+ * @type {Object}
+ */
+ 'private _queuedApiCall': {},
+
+ /**
+ * Stack depth for field updates (recursion detection)
+ * @type {Object}
+ */
+ 'private _fieldUpdateDepth': {},
+
+ /**
+ * Whether new field data has been emitted
+ * @type {Object}
+ */
+ 'private _fieldDataEmitted': {},
+
+ /**
+ * Id of timer to process API queue
+ * @type {number}
+ */
+ 'private _fieldApiTimer': 0,
+
+ /**
+ * Fields that require API requests
+ * @type {Object}}
+ */
+ 'private _fieldStale': {},
+
+ /**
+ * API descriptions
+ * @type {Object}
+ */
+ 'private _apis': {},
+
+
+ __construct: function( api_factory )
+ {
+ this._dataApiFactory = api_factory;
+ },
+
+
+ /**
+ * Set available APIs
+ *
+ * TODO: Remove me; pass via ctor
+ * TODO: Document API definition format
+ *
+ * @param {Object} apis API definitions
+ *
+ * @return {DataApiManager} self
+ */
+ 'public setApis': function( apis )
+ {
+ this._apis = apis;
+ return this;
+ },
+
+
+ /**
+ * Retrieve data from the API identified by the given id
+ *
+ * The optional request id permits cancelling requests if necessary.
+ *
+ * TODO: refactor argument list; it's just been built upon too much and
+ * needs reordering
+ *
+ * @param {string} api API id
+ * @param {Object} data API arguments (key-value)
+ * @param {function(Object)} callback callback to contain response
+ * @param {string} name element name for tracking
+ * @param {number} index index for tracking
+ * @param {bucket} bucket optional bucket to use as data source
+ * @param {function(Error)} fc failure continuation
+ *
+ * @return {Program} self
+ */
+ 'public getApiData': function( api, data, callback, name, index, bucket, fc )
+ {
+ var id = ( name === undefined )
+ ? ( ( new Date() ).getTime() )
+ : name + '_' + index;
+
+ var _self = this;
+
+ if ( !( this._apis[ api ] ) )
+ {
+ this.emit( 'error', Error( 'Unknown data API: ' + api ) );
+ }
+
+ // create the API if necessary (lazy-load); otherwise, use the existing
+ // instance
+ var api = this._dataApis[ api ] || ( function()
+ {
+ var apidesc = _self._apis[ api ];
+
+ // create a new instance of the API
+ return _self._dataApis[ api ] = _self._dataApiFactory.fromType(
+ apidesc.type, apidesc, bucket
+ ).on( 'error', function( e )
+ {
+ _self.emit( 'error', e );
+ } );
+ } )();
+
+ // this has the effect of wiping out previous requests of the same id,
+ // ensuring that we will make only the most recent request
+ this._queuedApiCall[ id ] = function()
+ {
+ // mark this request as pending (note that we aren't storing and
+ // references to this object because we do not want a reference to
+ // be used---the entire object may be reassigned by something else
+ // in order to wipe out all values)
+ var uid = ( ( new Date() ).getTime() );
+ _self._pendingApiCall[ id ] = {
+ uid: uid,
+ name: name,
+ index: index
+ };
+
+ // process the request; we'll let them know when it comes back
+ try
+ {
+ api.request( data, function()
+ {
+ // we only wish to populate the field if the request should
+ // still be considered pending
+ var curuid = ( _self._pendingApiCall[ id ] || {} ).uid;
+ if ( curuid === uid )
+ {
+ // forward to the caller
+ callback.apply( this, arguments );
+
+ // clear the pending flag
+ _self._pendingApiCall[ id ] = undefined;
+ }
+ } );
+ }
+ catch ( e )
+ {
+ fc( e );
+ }
+ };
+
+ // field is about to be re-loaded
+ this.fieldStale( name, index, false );
+
+ this._setFieldApiTimer();
+ return this;
+ },
+
+
+ /**
+ * Get pending API calls
+ *
+ * TODO: Added to support a progressive refactoring; this breaks
+ * encapsulation and should be removed, or formalized.
+ *
+ * Returned object contains uid, name, and index fields.
+ *
+ * @return {Object} pending API calls
+ */
+ 'public getPendingApiCalls': function()
+ {
+ return this._pendingApiCall;
+ },
+
+
+ /**
+ * Marks field for re-loading
+ *
+ * Stale fields will not be considered to have data, but the data
+ * will remain in memory until the next request.
+ *
+ * @param {string} field field name
+ * @param {number} index field index
+ * @param {?boolean} stale whether field is stale
+ *
+ * @return {DataApiManager} self
+ */
+ 'public fieldStale': function( field, index, stale )
+ {
+ stale = ( stale === undefined ) ? true : !!stale;
+
+ this._fieldStale[ field ] = this.fieldStale[ field ] || [];
+ this._fieldStale[ field ][ index ] = stale;
+
+ return this;
+ },
+
+
+ /**
+ * Whether field is marked stale
+ *
+ * @param {string} field field name
+ * @param {number} index field index
+ *
+ * @return {boolean} whether field is stale
+ */
+ 'protected isFieldStale': function( field, index )
+ {
+ return ( this._fieldStale[ field ] || [] )[ index ] === true;
+ },
+
+
+ 'public fieldNotReady': function( id, i, bucket )
+ {
+ if ( !( this.hasFieldData( id, i ) ) )
+ {
+ return;
+ }
+
+ // failure means that we don't have all the necessary params; clear the
+ // field
+ this.clearFieldData( id, i );
+
+ // clear the value of this field (IMPORTANT: do this *after* clearing
+ // the field data, since the empty value may otherwise be invalid);
+ // ***note that this will also clear any bucket values associated with
+ // this field, because this will trigger the change event for this
+ // field***
+ if ( bucket.hasIndex( id, i ) )
+ {
+ var data={};
+ data[ id ] = [];
+ data[ id ][ i ] = '';
+
+ // the second argument ensures that we merge indexes, rather than
+ // overwrite the entire value (see FS#11224)
+ bucket.setValues( data, true );
+ }
+ },
+
+
+ 'private _setFieldApiTimer': function()
+ {
+ // no use in re-setting
+ if ( this._fieldApiTimer )
+ {
+ return;
+ }
+
+ var _self = this;
+ this._fieldApiTimer = setTimeout( function()
+ {
+ _self.processFieldApiCalls();
+ }, 0 );
+ },
+
+
+ 'public processFieldApiCalls': function()
+ {
+ // this may trigger more requests, so be prepared with a fresh queue
+ var oldqueue = this._queuedApiCall;
+ this._fieldApiTimer = 0;
+ this._queuedApiCall = {};
+
+ for ( var c in oldqueue )
+ {
+ if ( oldqueue[c] === undefined )
+ {
+ continue;
+ }
+
+ // perform the API call.
+ oldqueue[c]();
+ }
+
+ return this;
+ },
+
+
+ /**
+ * Set API return data for a given field
+ *
+ * @param {string} name field name
+ * @param {number} index field index
+ * @param {Array.