diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/timeseriesprofile/TimeSeriesProfileDao.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/timeseriesprofile/TimeSeriesProfileDao.java
new file mode 100644
index 000000000..b08ea9ad8
--- /dev/null
+++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/timeseriesprofile/TimeSeriesProfileDao.java
@@ -0,0 +1,118 @@
+package cwms.cda.data.dao.timeseriesprofile;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import cwms.cda.data.dao.JooqDao;
+import cwms.cda.data.dto.CwmsId;
+import cwms.cda.data.dto.timeseriesprofile.TimeSeriesProfile;
+import org.jetbrains.annotations.NotNull;
+import org.jooq.Condition;
+import org.jooq.DSLContext;
+import org.jooq.Record;
+import org.jooq.Result;
+import org.jooq.impl.DSL;
+
+import usace.cwms.db.jooq.codegen.packages.CWMS_TS_PROFILE_PACKAGE;
+import usace.cwms.db.jooq.codegen.udt.records.STR_TAB_T;
+import usace.cwms.db.jooq.codegen.udt.records.TS_PROFILE_T;
+
+import static usace.cwms.db.jooq.codegen.tables.AV_TS_PROFILE.AV_TS_PROFILE;
+
+public class TimeSeriesProfileDao extends JooqDao<TimeSeriesProfile> {
+    public TimeSeriesProfileDao(DSLContext dsl) {
+        super(dsl);
+    }
+
+    public void storeTimeSeriesProfile(TimeSeriesProfile timeSeriesProfile, boolean failIfExists) {
+
+        connection(dsl, conn -> {
+            List<String> parameterList = timeSeriesProfile.getParameterList();
+            StringBuilder parameterString = new StringBuilder(parameterList.get(0));
+
+            for (int i = 1; i < parameterList.size(); i++) {
+                parameterString.append(",").append(parameterList.get(i));
+            }
+            String referenceTsId = null;
+            if (timeSeriesProfile.getReferenceTsId() != null) {
+                referenceTsId = timeSeriesProfile.getReferenceTsId().getName();
+            }
+            CWMS_TS_PROFILE_PACKAGE.call_STORE_TS_PROFILE(DSL.using(conn).configuration(), timeSeriesProfile.getLocationId().getName(),
+                    timeSeriesProfile.getKeyParameter(),
+                    parameterString.toString(),
+                    timeSeriesProfile.getDescription(), referenceTsId, failIfExists?"T":"F", "T", timeSeriesProfile.getLocationId().getOfficeId());
+        });
+    }
+
+    public TimeSeriesProfile retrieveTimeSeriesProfile(String locationId, String parameterId, String officeId) {
+        return connectionResult(dsl, conn -> {
+            TS_PROFILE_T timeSeriesProfile = CWMS_TS_PROFILE_PACKAGE.call_RETRIEVE_TS_PROFILE(
+                    DSL.using(conn).configuration(), locationId, parameterId, officeId);
+            return map(timeSeriesProfile, locationId, parameterId, officeId);
+        });
+    }
+
+    public void deleteTimeSeriesProfile(String locationId, String keyParameter, String officeId) {
+        connection(dsl, conn ->
+                CWMS_TS_PROFILE_PACKAGE.call_DELETE_TS_PROFILE(DSL.using(conn).configuration(), locationId, keyParameter, "DELETE ALL",
+                        officeId));
+    }
+
+    public void copyTimeSeriesProfile(String locationId, String keyParameter, String destinationLocation, String destRefTsId, String officeId) {
+        connection(dsl, conn ->
+                CWMS_TS_PROFILE_PACKAGE.call_COPY_TS_PROFILE(DSL.using(conn).configuration(), locationId, keyParameter, destinationLocation,
+                        destRefTsId, "F", "F",
+                        officeId));
+    }
+     public List<TimeSeriesProfile> catalogTimeSeriesProfiles(String locationIdMask, String parameterIdMask, String officeIdMask) {
+         List<TimeSeriesProfile> timeSeriesProfileList = new ArrayList<>();
+
+        Condition whereCondition = JooqDao.caseInsensitiveLikeRegexNullTrue(AV_TS_PROFILE.LOCATION_ID, locationIdMask);
+        whereCondition = whereCondition.and(JooqDao.caseInsensitiveLikeRegex(AV_TS_PROFILE.OFFICE_ID, officeIdMask));
+        whereCondition = whereCondition.and(JooqDao.caseInsensitiveLikeRegex(AV_TS_PROFILE.KEY_PARAMETER_ID, parameterIdMask));
+
+         @NotNull Result<Record> timeSeriesProfileResults =  dsl.select(DSL.asterisk()).from(AV_TS_PROFILE)
+                 .where(whereCondition)
+                 .fetch();
+         for (Record timeSeriesProfileResult : timeSeriesProfileResults) {
+             String parameters = timeSeriesProfileResult.get("PARAMETERS", String.class);
+             String[] parameterArray = parameters.split(",");
+             List<String> parameterList = Arrays.asList(parameterArray);
+
+             CwmsId locationId = new CwmsId.Builder()
+                     .withName((String) timeSeriesProfileResult.get("LOCATION_ID"))
+                     .withOfficeId((String) timeSeriesProfileResult.get("OFFICE_ID"))
+                     .build();
+             CwmsId referenceTsId = new CwmsId.Builder()
+                     .withName((String) timeSeriesProfileResult.get("ELEV_TS_ID"))
+                     .withOfficeId((String) timeSeriesProfileResult.get("OFFICE_ID"))
+                     .build();
+             timeSeriesProfileList.add(new TimeSeriesProfile.Builder()
+                     .withDescription((String) timeSeriesProfileResult.get("DESCRIPTION"))
+                     .withReferenceTsId(referenceTsId)
+                     .withKeyParameter((String) timeSeriesProfileResult.get("KEY_PARAMETER_ID"))
+                     .withLocationId(locationId)
+                     .withParameterList(parameterList)
+                     .build());
+         }
+         return timeSeriesProfileList;
+    }
+
+    private TimeSeriesProfile map(TS_PROFILE_T timeSeriesProfile, String locationName, String keyParameter, String officeId) {
+        STR_TAB_T profileParams = timeSeriesProfile.getPROFILE_PARAMS();
+        List<String> parameterList = new ArrayList<>(profileParams);
+        CwmsId locationId = new CwmsId.Builder().withName(locationName).withOfficeId(officeId).build();
+        CwmsId referenceTsId = null;
+        if (timeSeriesProfile.getREFERENCE_TS_ID() != null) {
+            referenceTsId = new CwmsId.Builder().withName(timeSeriesProfile.getREFERENCE_TS_ID()).withOfficeId(officeId).build();
+        }
+        return new TimeSeriesProfile.Builder()
+                .withLocationId(locationId)
+                .withDescription(timeSeriesProfile.getDESCRIPTION())
+                .withReferenceTsId(referenceTsId)
+                .withKeyParameter(keyParameter)
+                .withParameterList(parameterList)
+                .build();
+    }
+}
diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/timeseriesprofile/TimeSeriesProfileInstanceDao.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/timeseriesprofile/TimeSeriesProfileInstanceDao.java
new file mode 100644
index 000000000..adaa24328
--- /dev/null
+++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/timeseriesprofile/TimeSeriesProfileInstanceDao.java
@@ -0,0 +1,317 @@
+package cwms.cda.data.dao.timeseriesprofile;
+
+import cwms.cda.data.dao.JooqDao;
+import cwms.cda.data.dto.CwmsId;
+import cwms.cda.data.dto.timeseriesprofile.ProfileTimeSeries;
+import cwms.cda.data.dto.timeseriesprofile.TimeSeriesProfile;
+import cwms.cda.data.dto.timeseriesprofile.TimeSeriesProfileInstance;
+import cwms.cda.data.dto.timeseriesprofile.TimeValuePair;
+import org.jetbrains.annotations.NotNull;
+import org.jooq.Condition;
+import org.jooq.Configuration;
+import org.jooq.DSLContext;
+import org.jooq.Record;
+import org.jooq.Result;
+import org.jooq.impl.DSL;
+import usace.cwms.db.jooq.codegen.packages.CWMS_LOC_PACKAGE;
+import usace.cwms.db.jooq.codegen.packages.CWMS_TS_PROFILE_PACKAGE;
+import usace.cwms.db.jooq.codegen.packages.CWMS_UTIL_PACKAGE;
+import usace.cwms.db.jooq.codegen.tables.AV_TS_PROFILE_INST;
+import usace.cwms.db.jooq.codegen.udt.records.PVQ_T;
+import usace.cwms.db.jooq.codegen.udt.records.PVQ_TAB_T;
+import usace.cwms.db.jooq.codegen.udt.records.STR_TAB_T;
+import usace.cwms.db.jooq.codegen.udt.records.TS_PROF_DATA_REC_T;
+import usace.cwms.db.jooq.codegen.udt.records.TS_PROF_DATA_T;
+import usace.cwms.db.jooq.codegen.udt.records.TS_PROF_DATA_TAB_T;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class TimeSeriesProfileInstanceDao extends JooqDao<TimeSeriesProfileInstance>
+{
+	public TimeSeriesProfileInstanceDao(DSLContext dsl)
+	{
+		super(dsl);
+	}
+
+	void storeTimeSeriesProfileInstance(TimeSeriesProfile timeSeriesProfile, String profileData, Instant versionDate,
+			String versionId, String storeRule, boolean overrideProtection)
+	{
+		connection(dsl, conn -> CWMS_TS_PROFILE_PACKAGE.call_STORE_TS_PROFILE_INSTANCE__2(DSL.using(conn).configuration(),
+                timeSeriesProfile.getLocationId().getName(),
+                timeSeriesProfile.getKeyParameter(),
+                profileData,
+                versionId,
+                storeRule,
+                overrideProtection?"T":"F",
+                versionDate!=null?Timestamp.from(versionDate):null,
+                timeSeriesProfile.getLocationId().getOfficeId()));
+	}
+
+	void storeTimeSeriesProfileInstance(TimeSeriesProfileInstance timeseriesProfileInstance, String versionId, Instant versionInstant, String storeRule,String overrideProtection)
+	{
+		connection(dsl, conn -> {
+			BigDecimal locationCodeId = CWMS_LOC_PACKAGE.call_GET_LOCATION_CODE(DSL.using(conn).configuration(),
+					timeseriesProfileInstance.getTimeSeriesProfile().getLocationId().getOfficeId(),
+					timeseriesProfileInstance.getTimeSeriesProfile().getLocationId().getName());
+
+
+			Map<String, BigInteger> parameterIdToCode = new HashMap<>();
+
+			String parameter = timeseriesProfileInstance.getTimeSeriesProfile().getKeyParameter();
+			BigDecimal parameterCodeDec = CWMS_UTIL_PACKAGE.call_GET_PARAMETER_CODE(DSL.using(conn).configuration(), parameter,
+					timeseriesProfileInstance.getTimeSeriesProfile().getLocationId().getOfficeId());
+			parameterIdToCode.put(parameter, parameterCodeDec.toBigInteger());
+
+			List<ProfileTimeSeries> timeSeriesList = timeseriesProfileInstance.getTimeSeriesList();
+			for(ProfileTimeSeries profileTimeSeries : timeSeriesList)
+			{
+				parameter = profileTimeSeries.getParameter();
+				parameterCodeDec = CWMS_UTIL_PACKAGE.call_GET_PARAMETER_CODE(DSL.using(conn).configuration(), parameter,
+						timeseriesProfileInstance.getTimeSeriesProfile().getLocationId().getOfficeId());
+				parameterIdToCode.put(parameter, parameterCodeDec.toBigInteger());
+			}
+
+			TS_PROF_DATA_T tsProfileData = new TS_PROF_DATA_T();
+			tsProfileData.attach(DSL.using(conn).configuration());
+
+			TS_PROF_DATA_TAB_T records = new TS_PROF_DATA_TAB_T();
+
+			for(int i=0; i<timeseriesProfileInstance.getTimeSeriesList().get(0).getValues().size();i++)
+			{
+				TS_PROF_DATA_REC_T dataRecord = new TS_PROF_DATA_REC_T();
+				Timestamp timeStamp =  Timestamp.from(timeseriesProfileInstance.getTimeSeriesList().get(0)
+								.getValues().get(i).getDateTime());
+				dataRecord.setDATE_TIME(timeStamp);
+				records.add(dataRecord);
+			}
+			STR_TAB_T units = new STR_TAB_T();
+			for (TS_PROF_DATA_REC_T ts_prof_data_rec_t : records) {
+				PVQ_TAB_T parameters = new PVQ_TAB_T();
+				for (int i = 0; i < timeseriesProfileInstance.getTimeSeriesList().size(); i++) {
+					PVQ_T pvq = new PVQ_T();
+					String parameterId = timeseriesProfileInstance.getTimeSeriesList().get(i).getParameter();
+					BigInteger parameterCode = parameterIdToCode.get(parameterId);
+					pvq.setPARAMETER_CODE(parameterCode);
+					parameters.add(pvq);
+					units.add(timeseriesProfileInstance.getTimeSeriesList().get(i).getUnit());
+				}
+				ts_prof_data_rec_t.setPARAMETERS(parameters);
+			}
+
+			for(int i = 0; i<timeseriesProfileInstance.getTimeSeriesList().size(); i++)
+			{
+				for(int j =0; j<timeseriesProfileInstance.getTimeSeriesList().get(i).getValues().size();j++)
+				{
+					TS_PROF_DATA_REC_T dataRecord = records.get(j);
+					dataRecord.getPARAMETERS().get(i).setVALUE(
+						timeseriesProfileInstance.getTimeSeriesList().get(i).getValues().get(j).getValue());
+				}
+			}
+
+			tsProfileData.setRECORDS(records);
+			tsProfileData.setLOCATION_CODE(locationCodeId.toBigInteger());
+			tsProfileData.setTIME_ZONE("UTC");
+			tsProfileData.setKEY_PARAMETER(parameterIdToCode.get(timeseriesProfileInstance.getTimeSeriesProfile().getKeyParameter()));
+			tsProfileData.setUNITS(units);
+
+			Timestamp versionTimeStamp =  Timestamp.from(versionInstant);
+
+
+
+				CWMS_TS_PROFILE_PACKAGE.call_STORE_TS_PROFILE_INSTANCE(DSL.using(conn).configuration(),
+						tsProfileData,
+						versionId,
+						storeRule,
+						overrideProtection,
+						versionTimeStamp,
+						timeseriesProfileInstance.getTimeSeriesProfile().getLocationId().getOfficeId());
+				}
+			);
+	}
+
+
+	List<TimeSeriesProfileInstance> catalogTimeSeriesProfileInstances( String officeIdMask, String locationIdMask, String parameterIdMask, String versionMask)
+	{
+		List<TimeSeriesProfileInstance> timeSeriesProfileInstanceList = new ArrayList<>();
+
+		Condition whereCondition = JooqDao.caseInsensitiveLikeRegexNullTrue(AV_TS_PROFILE_INST.AV_TS_PROFILE_INST.LOCATION_ID, locationIdMask);
+		whereCondition = whereCondition.and(JooqDao.caseInsensitiveLikeRegex(AV_TS_PROFILE_INST.AV_TS_PROFILE_INST.OFFICE_ID, officeIdMask));
+		whereCondition = whereCondition.and(JooqDao.caseInsensitiveLikeRegex(AV_TS_PROFILE_INST.AV_TS_PROFILE_INST.KEY_PARAMETER_ID, parameterIdMask));
+		whereCondition = whereCondition.and(JooqDao.caseInsensitiveLikeRegex(AV_TS_PROFILE_INST.AV_TS_PROFILE_INST.VERSION_ID, versionMask));
+
+		@NotNull Result<Record> timeSeriesProfileInstanceResults =  dsl.select(DSL.asterisk()).from(AV_TS_PROFILE_INST.AV_TS_PROFILE_INST)
+				.where(whereCondition)
+				.fetch();
+		for (Record result : timeSeriesProfileInstanceResults) {
+				CwmsId locationId = new CwmsId.Builder()
+						.withOfficeId(result.get("OFFICE_ID",String.class))
+						.withName(result.get("LOCATION_ID", String.class))
+						.build();
+				String parameterId = result.get("KEY_PARAMETER_ID", String.class);
+				TimeSeriesProfile timeSeriesProfile = new TimeSeriesProfile.Builder()
+						.withLocationId(locationId)
+						.withKeyParameter(parameterId)
+						.build();
+				TimeSeriesProfileInstance timeSeriesProfileInstance = new TimeSeriesProfileInstance.Builder()
+						.withTimeSeriesProfile(timeSeriesProfile)
+						.withVersion(result.get("VERSION_ID", String.class))
+						.withVersionDate(result.get("VERSION_DATE", Instant.class))
+						.withFirstDate(result.get("FIRST_DATE_TIME", Instant.class))
+						.withLastDate(result.get("LAST_DATE_TIME", Instant.class))
+						.build();
+
+			timeSeriesProfileInstanceList.add(timeSeriesProfileInstance);
+		}
+		return timeSeriesProfileInstanceList;
+	}
+
+	TimeSeriesProfileInstance retrieveTimeSeriesProfileInstance(CwmsId location, String keyParameter,
+		String version,
+		String unit,
+		Instant startTime,
+		Instant endTime,
+		String timeZone,
+		String startInclusive,
+		String endInclusive,
+		String previous,
+		String next,
+		Instant versionDate,
+		String maxVersion)
+	{
+		return connectionResult(dsl, conn -> {
+			TS_PROF_DATA_T timeSeriesProfileData;
+				timeSeriesProfileData = CWMS_TS_PROFILE_PACKAGE.call_RETRIEVE_TS_PROFILE_DATA(
+						DSL.using(conn).configuration(),
+						location.getName(),
+						keyParameter,
+						version,
+						unit,
+						Timestamp.from(startTime),
+						Timestamp.from(endTime),
+						timeZone,
+						startInclusive,
+						endInclusive,
+						previous,
+						next,
+						versionDate!=null?Timestamp.from(versionDate):null,
+						maxVersion,
+						location.getOfficeId()
+				);
+
+			return map(DSL.using(conn).configuration(), location.getOfficeId(), timeSeriesProfileData, version, versionDate);
+		});
+	}
+	void deleteTimeSeriesProfileInstance(CwmsId location, String keyParameter,
+			String version, Instant firstDate, String timeZone,boolean overrideProtection, Instant versionDate)
+	{
+		connection(dsl, conn -> {
+
+			Timestamp versionTimestamp = null;
+			if(versionDate!=null)
+			{
+				versionTimestamp = Timestamp.from(versionDate);
+			}
+
+				CWMS_TS_PROFILE_PACKAGE.call_DELETE_TS_PROFILE_INSTANCE(
+						DSL.using(conn).configuration(),
+						location.getName(),
+						keyParameter,
+						version,
+						Timestamp.from(firstDate),
+						timeZone,
+						overrideProtection?"T":"F",
+						versionTimestamp,
+						location.getOfficeId()
+				);
+
+		});
+	}
+
+
+	private TimeSeriesProfileInstance map(@NotNull Configuration configuration, String officeId, TS_PROF_DATA_T timeSeriesProfileData, String version, Instant versionDate)  {
+		String timeZone = timeSeriesProfileData.getTIME_ZONE();
+		STR_TAB_T units = timeSeriesProfileData.getUNITS();
+		TS_PROF_DATA_TAB_T records = timeSeriesProfileData.getRECORDS();
+		BigInteger locationCode = timeSeriesProfileData.getLOCATION_CODE();
+		String location = CWMS_UTIL_PACKAGE.call_GET_LOCATION_ID(configuration, locationCode, officeId);
+		BigInteger keyParameterCode = timeSeriesProfileData.getKEY_PARAMETER();
+		String keyParameter = CWMS_UTIL_PACKAGE.call_GET_PARAMETER_ID(configuration, keyParameterCode);
+		List<Instant> timeList = new ArrayList<>();
+		List<List<Double>> valuesList = new ArrayList<>();
+		List<List<Integer>> qualitiesList = new ArrayList<>();
+		List<List<BigInteger>> parametersList = new ArrayList<>();
+		for(TS_PROF_DATA_REC_T dataRecord : records)
+		{
+			Instant dateTime = dataRecord.get(0, Instant.class);
+			timeList.add(dateTime);
+			PVQ_TAB_T parameters = dataRecord.getPARAMETERS();
+			List<Double> valueList = new ArrayList<>();
+			List<Integer> qualityList = new ArrayList<>();
+			List<BigInteger> parameterList = new ArrayList<>();
+			for(PVQ_T parameter : parameters)
+			{
+				valueList.add(parameter.getVALUE());
+				qualityList.add(parameter.getQUALITY_CODE().intValue());
+				parameterList.add(parameter.getPARAMETER_CODE());
+			}
+			valuesList.add(valueList);
+			parametersList.add(parameterList);
+			qualitiesList.add(qualityList);
+		}
+		List<String> parameterList = new ArrayList<>();
+		List<List<TimeValuePair>> timeValuePairList = new ArrayList<>();
+		if(!parametersList.isEmpty()) {
+			for (int i = 0; i < parametersList.get(0).size(); i++) {
+				String parameter = CWMS_UTIL_PACKAGE.call_GET_PARAMETER_ID(configuration, parametersList.get(0).get(i));
+				parameterList.add(parameter);
+				timeValuePairList.add(new ArrayList<>());
+			}
+		}
+		if(!valuesList.isEmpty())
+		{
+			for(int i = 0; i<valuesList.size(); i++) {
+				for (int j = 0; j < valuesList.get(i).size(); j++) {
+					TimeValuePair timeValuePair = new TimeValuePair.Builder()
+							.withDateTime(timeList.get(i))
+							.withValue(valuesList.get(i).get(j))
+							.withQuality(qualitiesList.get(i).get(j))
+							.build();
+					timeValuePairList.get(j).add(timeValuePair);
+				}
+			}}
+		List<ProfileTimeSeries> timeSeriesList = new ArrayList<>();
+		if(!timeValuePairList.isEmpty()) {
+			for (int i = 0; i < timeValuePairList.size(); i++) {
+				ProfileTimeSeries timeSeries = new ProfileTimeSeries.Builder()
+						.withValues(timeValuePairList.get(i))
+						.withTimeZone(timeZone)
+						.withParameter(parameterList.get(i))
+						.withUnit(units.get(i))
+						.build();
+				timeSeriesList.add(timeSeries);
+			}
+		}
+		CwmsId locationId = new CwmsId.Builder()
+				.withOfficeId(officeId)
+				.withName(location)
+				.build();
+		TimeSeriesProfile timeSeriesProfile = new TimeSeriesProfile.Builder()
+				.withKeyParameter(keyParameter)
+				.withLocationId(locationId)
+				.withParameterList(parameterList)
+				.build();
+		return new TimeSeriesProfileInstance.Builder()
+				.withTimeSeriesProfile(timeSeriesProfile)
+				.withTimeSeriesList(timeSeriesList)
+				.withVersion(version)
+				.withVersionDate(versionDate)
+				.build();
+	}
+}
\ No newline at end of file
diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/timeseriesprofile/TimeSeriesProfileParserDao.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/timeseriesprofile/TimeSeriesProfileParserDao.java
new file mode 100644
index 000000000..3bc04145d
--- /dev/null
+++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/timeseriesprofile/TimeSeriesProfileParserDao.java
@@ -0,0 +1,278 @@
+package cwms.cda.data.dao.timeseriesprofile;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import cwms.cda.data.dao.JooqDao;
+import cwms.cda.data.dto.CwmsId;
+import cwms.cda.data.dto.timeseriesprofile.ParameterInfo;
+import cwms.cda.data.dto.timeseriesprofile.ParameterInfoColumnar;
+import cwms.cda.data.dto.timeseriesprofile.ParameterInfoIndexed;
+import cwms.cda.data.dto.timeseriesprofile.TimeSeriesProfileParser;
+import cwms.cda.data.dto.timeseriesprofile.TimeSeriesProfileParserColumnar;
+import cwms.cda.data.dto.timeseriesprofile.TimeSeriesProfileParserIndexed;
+import org.jetbrains.annotations.NotNull;
+import org.jooq.Condition;
+import org.jooq.DSLContext;
+import org.jooq.Record;
+import org.jooq.Result;
+import org.jooq.impl.DSL;
+
+import usace.cwms.db.jooq.codegen.packages.CWMS_TS_PROFILE_PACKAGE;
+import usace.cwms.db.jooq.codegen.packages.cwms_ts_profile.RETRIEVE_TS_PROFILE_PARSER;
+import usace.cwms.db.jooq.codegen.tables.AV_TS_PROFILE_PARSER;
+import usace.cwms.db.jooq.codegen.tables.AV_TS_PROFILE_PARSER_PARAM;
+
+
+public class TimeSeriesProfileParserDao extends JooqDao<TimeSeriesProfileParser> {
+    private static final  String PARAMETER_ID = "PARAMETER_ID";
+    private static final  String KEY_PARAMETER_ID = "KEY_PARAMTER_ID";
+    private static final  String TIME_FORMAT = "TIME_FORMAT";
+    private static final  String TIME_ZONE = "TIME_ZONE";
+    public TimeSeriesProfileParserDao(DSLContext dsl) {
+        super(dsl);
+    }
+
+    private List<ParameterInfo> getParameterInfoList(String info, String recordDelimiter, String fieldDelimiter) {
+        List<ParameterInfo> parameterInfoList = new ArrayList<>();
+        String[] records = info.split(recordDelimiter);
+        for (String aRecord : records) {
+            String[] fields = aRecord.split(fieldDelimiter);
+            int index = Integer.parseInt(fields[2]);
+            ParameterInfo parameterInfo = new ParameterInfoIndexed.Builder().withIndex(index)
+                    .withParameter(fields[0])
+                    .withUnit(fields[1])
+                    .build();
+            parameterInfoList.add(parameterInfo);
+        }
+        return parameterInfoList;
+    }
+
+    private String getParameterInfoString(TimeSeriesProfileParser timeSeriesProfileParser) {
+        List<ParameterInfo> parameterInfo = timeSeriesProfileParser.getParameterInfoList();
+
+        StringBuilder parameterInfoBuilder = new StringBuilder();
+        parameterInfoBuilder.append(parameterInfo.get(0).getParameterInfoString());
+
+        for (int i = 1; i < parameterInfo.size(); i++) {
+            parameterInfoBuilder.append(timeSeriesProfileParser.getRecordDelimiter())
+                    .append(parameterInfo.get(i).getParameterInfoString());
+        }
+        return parameterInfoBuilder.toString();
+    }
+
+    public void storeTimeSeriesProfileParser(TimeSeriesProfileParserIndexed timeSeriesProfileParser, boolean failIfExists) {
+        connection(dsl, conn ->
+                CWMS_TS_PROFILE_PACKAGE.call_STORE_TS_PROFILE_PARSER(DSL.using(conn).configuration(), timeSeriesProfileParser.getLocationId().getName(),
+                        timeSeriesProfileParser.getKeyParameter(), String.valueOf(timeSeriesProfileParser.getRecordDelimiter()),
+                        String.valueOf(timeSeriesProfileParser.getFieldDelimiter()), timeSeriesProfileParser.getTimeField(),
+                        null, null, timeSeriesProfileParser.getTimeFormat(),
+                        timeSeriesProfileParser.getTimeZone(), getParameterInfoString(timeSeriesProfileParser),
+                        timeSeriesProfileParser.getTimeInTwoFields() ? "T" : "F",
+                        failIfExists ? "T" : "F", "T", timeSeriesProfileParser.getLocationId().getOfficeId())
+        );
+    }
+
+    public void storeTimeSeriesProfileParser(TimeSeriesProfileParserColumnar timeSeriesProfileParser, boolean failIfExists) {
+        connection(dsl, conn ->
+                CWMS_TS_PROFILE_PACKAGE.call_STORE_TS_PROFILE_PARSER(DSL.using(conn).configuration(), timeSeriesProfileParser.getLocationId().getName(),
+                        timeSeriesProfileParser.getKeyParameter(), String.valueOf(timeSeriesProfileParser.getRecordDelimiter()),
+                        null, null,
+                        timeSeriesProfileParser.getTimeStartColumn(), timeSeriesProfileParser.getTimeEndColumn(), timeSeriesProfileParser.getTimeFormat(),
+                        timeSeriesProfileParser.getTimeZone(), getParameterInfoString(timeSeriesProfileParser),
+                        timeSeriesProfileParser.getTimeInTwoFields() ? "T" : "F",
+                        failIfExists ? "T" : "F", "F", timeSeriesProfileParser.getLocationId().getOfficeId())
+        );
+    }
+
+    public TimeSeriesProfileParser retrieveTimeSeriesProfileParser(String locationId, String parameterId, String officeId) {
+        return connectionResult(dsl, conn -> {
+            RETRIEVE_TS_PROFILE_PARSER timeSeriesProfileParser = CWMS_TS_PROFILE_PACKAGE.call_RETRIEVE_TS_PROFILE_PARSER(
+                    DSL.using(conn).configuration(), locationId, parameterId, officeId);
+            return map(timeSeriesProfileParser, locationId, parameterId, officeId);
+        });
+    }
+
+    public List<ParameterInfo> retrieveParameterInfoList(String locationId, String parameterId, String officeId) {
+        List<ParameterInfo> parameterInfoList = new ArrayList<>();
+        Condition whereCondition = JooqDao.caseInsensitiveLikeRegexNullTrue(AV_TS_PROFILE_PARSER_PARAM.AV_TS_PROFILE_PARSER_PARAM.LOCATION_ID, locationId);
+        whereCondition = whereCondition.and(JooqDao.caseInsensitiveLikeRegex(AV_TS_PROFILE_PARSER_PARAM.AV_TS_PROFILE_PARSER_PARAM.OFFICE_ID, officeId));
+        whereCondition = whereCondition.and(JooqDao.caseInsensitiveLikeRegex(AV_TS_PROFILE_PARSER_PARAM.AV_TS_PROFILE_PARSER_PARAM.KEY_PARAMETER_ID, parameterId));
+        Result<Record> parameterInfoResults = dsl.select(DSL.asterisk()).from(AV_TS_PROFILE_PARSER_PARAM.AV_TS_PROFILE_PARSER_PARAM)
+                .where(whereCondition)
+                .fetch();
+        for (Record recordParameterInfo : parameterInfoResults) {
+            Short parameterField = recordParameterInfo.get("PARAMETER_FIELD", Short.class);
+            if (parameterField != null) {
+                parameterInfoList.add(new ParameterInfoIndexed.Builder()
+                        .withIndex(parameterField)
+                        .withParameter((String) recordParameterInfo.get(PARAMETER_ID))
+                        .withUnit((String) recordParameterInfo.get("PARAMETER_UNIT"))
+                        .build());
+            } else {
+                parameterInfoList.add(new ParameterInfoColumnar.Builder()
+                        .withStartColumn(recordParameterInfo.get("PARAMETER_COL_START", Short.class))
+                        .withEndColumn(recordParameterInfo.get("PARAMETER_COL_END", Short.class))
+                        .withParameter((String) recordParameterInfo.get(PARAMETER_ID))
+                        .withUnit((String) recordParameterInfo.get("PARAMETER_UNIT"))
+                        .build());
+            }
+        }
+        return parameterInfoList;
+    }
+
+    public List<TimeSeriesProfileParser> catalogTimeSeriesProfileParsers(String locationIdMask, String parameterIdMask, String officeIdMask, boolean includeParameters) {
+        List<TimeSeriesProfileParser> timeSeriesProfileParserList = new ArrayList<>();
+
+        Condition whereCondition = JooqDao.caseInsensitiveLikeRegexNullTrue(AV_TS_PROFILE_PARSER.AV_TS_PROFILE_PARSER.LOCATION_ID, locationIdMask);
+        whereCondition = whereCondition.and(JooqDao.caseInsensitiveLikeRegex(AV_TS_PROFILE_PARSER.AV_TS_PROFILE_PARSER.OFFICE_ID, officeIdMask));
+        whereCondition = whereCondition.and(JooqDao.caseInsensitiveLikeRegex(AV_TS_PROFILE_PARSER.AV_TS_PROFILE_PARSER.KEY_PARAMETER_ID, parameterIdMask));
+
+        @NotNull Result<Record> timeSeriesProfileParserResults = dsl.select(DSL.asterisk()).from(AV_TS_PROFILE_PARSER.AV_TS_PROFILE_PARSER)
+                .where(whereCondition)
+                .fetch();
+        for (Record profileParser : timeSeriesProfileParserResults) {
+            String recordDelimiter = profileParser.get("RECORD_DELIMTER_VALUE", String.class);
+            String fieldDelimiter = profileParser.get("FIELD_DELIMIETER_VALUE", String.class);
+            Short timeField = profileParser.get("TIME_FIELD", Short.class);
+            Short timeStartCol = profileParser.get("TIME_COL_START", Short.class);
+            Short timeEndCol = profileParser.get("TIME_COL_END", Short.class);
+            CwmsId locationId = new CwmsId.Builder()
+                    .withOfficeId((String) profileParser.get("OFFICE_ID"))
+                    .withName((String) profileParser.get("LOCATION_ID"))
+                    .build();
+            String keyParameter = profileParser.get("KEY_PARAMETER_ID", String.class);
+            TimeSeriesProfileParser timeSeriesProfileParser;
+            List<ParameterInfo> parameterInfoList = null;
+            if (includeParameters) {
+                parameterInfoList = retrieveParameterInfoList(locationId.getName(), keyParameter, locationId.getOfficeId());
+            }
+            if (timeField != null) {
+                timeSeriesProfileParser = new TimeSeriesProfileParserIndexed.Builder()
+                        .withFieldDelimiter(fieldDelimiter.toCharArray()[0])
+                        .withTimeField(timeField)
+                        .withLocationId(locationId)
+                        .withKeyParameter(keyParameter)
+                        .withParameterInfoList(parameterInfoList)
+                        .withTimeFormat((String) profileParser.get(TIME_FORMAT))
+                        .withTimeZone((String) profileParser.get("TIME_ZONE_ID"))
+                        .withRecordDelimiter(recordDelimiter.toCharArray()[0])
+                        .build();
+                timeSeriesProfileParserList.add(timeSeriesProfileParser);
+            } else if (timeStartCol != null && timeEndCol != null) {
+                timeSeriesProfileParser = new TimeSeriesProfileParserColumnar.Builder()
+                        .withTimeStartColumn(timeStartCol)
+                        .withTimeEndColumn(timeEndCol)
+                        .withLocationId(locationId)
+                        .withKeyParameter(keyParameter)
+                        .withParameterInfoList(parameterInfoList)
+                        .withTimeFormat((String) profileParser.get(TIME_FORMAT))
+                        .withTimeZone((String) profileParser.get("TIME_ZONE_ID"))
+                        .withRecordDelimiter(recordDelimiter.toCharArray()[0])
+                        .build();
+                timeSeriesProfileParserList.add(timeSeriesProfileParser);
+            }
+        }
+        return timeSeriesProfileParserList;
+    }
+
+    public List<TimeSeriesProfileParser> catalogTimeSeriesProfileParsers(String locationIdMask, String parameterIdMask, String officeIdMask) {
+        return connectionResult(dsl, conn -> {
+            Result<Record> tsProfileParserResult = CWMS_TS_PROFILE_PACKAGE.call_CAT_TS_PROFILE_PARSER(DSL.using(conn).configuration(),
+                    locationIdMask, parameterIdMask, officeIdMask);
+            List<TimeSeriesProfileParser> timeSeriesProfileParserList = new ArrayList<>();
+            for (Record profileParser : tsProfileParserResult) {
+                String recordDelimiter = profileParser.get("RECORD_DELIMITER", String.class);
+                String fieldDelimiter = profileParser.get("FIELD_DELIMITER", String.class);
+                Short timeField = profileParser.get("TIME_FIELD", Short.class);
+                Short timeStartCol = profileParser.get("TIME_START_COL", Short.class);
+                Short timeEndCol = profileParser.get("TIME_END_COL", Short.class);
+                Result<Record> parameterInfoResult = profileParser.get("PARAMETER_INFO", Result.class);
+
+                List<ParameterInfo> parameterInfoList = new ArrayList<>();
+                for (Record recordParam : parameterInfoResult) {
+                    if (timeField != null) {
+                        parameterInfoList.add(new ParameterInfoIndexed.Builder()
+                                .withIndex(recordParam.get("FIELD_NUMBER", Short.class))
+                                .withParameter((String) recordParam.get(PARAMETER_ID))
+                                .withUnit((String) recordParam.get("UNIT"))
+                                .build());
+                    } else {
+                        parameterInfoList.add(new ParameterInfoColumnar.Builder()
+                                .withStartColumn(recordParam.get("START_COL", Short.class))
+                                .withEndColumn(recordParam.get("END_COL", Short.class))
+                                .withParameter((String) recordParam.get(PARAMETER_ID))
+                                .withUnit((String) recordParam.get("UNIT"))
+                                .build());
+                    }
+                }
+
+
+                CwmsId locationId = new CwmsId.Builder()
+                        .withOfficeId((String) profileParser.get("OFFICE_ID"))
+                        .withName((String) profileParser.get("LOCATION_ID"))
+                        .build();
+                TimeSeriesProfileParser timeSeriesProfileParser;
+                if (timeField != null) {
+                    timeSeriesProfileParser = new TimeSeriesProfileParserIndexed.Builder()
+                            .withFieldDelimiter(fieldDelimiter.toCharArray()[0])
+                            .withTimeField(timeField)
+                            .withLocationId(locationId)
+                            .withKeyParameter(profileParser.get(KEY_PARAMETER_ID, String.class))
+                            .withTimeFormat((String) profileParser.get(TIME_FORMAT))
+                            .withTimeZone((String) profileParser.get(TIME_ZONE))
+                            .withRecordDelimiter(recordDelimiter.toCharArray()[0])
+                            .withParameterInfoList(parameterInfoList)
+                            .build();
+                    timeSeriesProfileParserList.add(timeSeriesProfileParser);
+                } else if (timeStartCol != null && timeEndCol != null) {
+                    timeSeriesProfileParser = new TimeSeriesProfileParserColumnar.Builder()
+                            .withTimeStartColumn(timeStartCol)
+                            .withTimeEndColumn(timeEndCol)
+                            .withLocationId(locationId)
+                            .withKeyParameter((String) profileParser.get(KEY_PARAMETER_ID))
+                            .withTimeFormat((String) profileParser.get(TIME_FORMAT))
+                            .withTimeZone((String) profileParser.get(TIME_ZONE))
+                            .withRecordDelimiter(recordDelimiter.toCharArray()[0])
+                            .withParameterInfoList(parameterInfoList)
+                            .build();
+                    timeSeriesProfileParserList.add(timeSeriesProfileParser);
+                }
+
+            }
+            return timeSeriesProfileParserList;
+        });
+    }
+
+
+    public void copyTimeSeriesProfileParser(String locationId, String parameterId, String officeId, String destinationLocation) {
+        connection(dsl, conn ->
+                CWMS_TS_PROFILE_PACKAGE.call_COPY_TS_PROFILE_PARSER(DSL.using(conn).configuration(), locationId, parameterId, destinationLocation,
+                        "F", officeId));
+    }
+
+    public void deleteTimeSeriesProfileParser(String locationId, String parameterId, String officeId) {
+        connection(dsl, conn ->
+                CWMS_TS_PROFILE_PACKAGE.call_DELETE_TS_PROFILE_PARSER(DSL.using(conn).configuration(), locationId,
+                        parameterId, officeId)
+        );
+    }
+
+    private TimeSeriesProfileParser map(RETRIEVE_TS_PROFILE_PARSER timeSeriesProfileParser, String locationName, String keyParameter, String officeId) {
+        String info = timeSeriesProfileParser.getP_PARAMETER_INFO();
+        List<ParameterInfo> parameterInfo = getParameterInfoList(info, timeSeriesProfileParser.getP_RECORD_DELIMITER(),
+                timeSeriesProfileParser.getP_FIELD_DELIMITER());
+        CwmsId locationId = new CwmsId.Builder().withOfficeId(officeId).withName(locationName).build();
+        return new TimeSeriesProfileParser.Builder()
+                .withLocationId(locationId)
+//				.withTimeField(timeSeriesProfileParser.getP_TIME_FIELD())
+                .withTimeZone(timeSeriesProfileParser.getP_TIME_ZONE())
+                .withTimeFormat(timeSeriesProfileParser.getP_TIME_FORMAT())
+                .withKeyParameter(keyParameter)
+//				.withFieldDelimiter(timeSeriesProfileParser.getP_FIELD_DELIMITER().toCharArray()[0])
+                .withRecordDelimiter(timeSeriesProfileParser.getP_RECORD_DELIMITER().toCharArray()[0])
+                .withTimeInTwoFields(false)
+                .withParameterInfoList(parameterInfo)
+                .build();
+    }
+}
diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/timeseriesprofile/ParameterInfo.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/timeseriesprofile/ParameterInfo.java
new file mode 100644
index 000000000..2d62cf29d
--- /dev/null
+++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/timeseriesprofile/ParameterInfo.java
@@ -0,0 +1,50 @@
+package cwms.cda.data.dto.timeseriesprofile;
+
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
+import cwms.cda.data.dto.CwmsDTOBase;
+
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
+@JsonSubTypes({@JsonSubTypes.Type(value = ParameterInfoIndexed.class, name = "indexed-parameter-info"),
+            @JsonSubTypes.Type(value = ParameterInfoColumnar.class, name = "columnar-parameter-info")
+})
+
+public abstract class ParameterInfo extends CwmsDTOBase {
+    private final String parameter;
+    private final String unit;
+
+    ParameterInfo(ParameterInfo.Builder builder) {
+        parameter = builder.parameter;
+        unit = builder.unit;
+    }
+    public abstract String getParameterInfoString();
+
+    public String getParameter() {
+        return parameter;
+    }
+
+    public String getUnit() {
+        return unit;
+    }
+
+    @JsonPOJOBuilder
+    @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
+    public abstract static class Builder {
+        private String parameter;
+        private String unit;
+
+        public Builder withParameter(String parameter) {
+            this.parameter = parameter;
+            return this;
+        }
+
+        public Builder withUnit(String unit) {
+            this.unit = unit;
+            return this;
+        }
+        public abstract ParameterInfo build();
+    }
+}
\ No newline at end of file
diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/timeseriesprofile/ParameterInfoColumnar.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/timeseriesprofile/ParameterInfoColumnar.java
new file mode 100644
index 000000000..844b282f3
--- /dev/null
+++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/timeseriesprofile/ParameterInfoColumnar.java
@@ -0,0 +1,74 @@
+package cwms.cda.data.dto.timeseriesprofile;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
+import cwms.cda.data.dto.CwmsDTOValidator;
+import cwms.cda.formatters.Formats;
+import cwms.cda.formatters.annotations.FormattableWith;
+import cwms.cda.formatters.json.JsonV2;
+
+
+@FormattableWith(contentType = Formats.JSONV2, formatter = JsonV2.class)
+@JsonDeserialize(builder = ParameterInfoColumnar.Builder.class)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
+public final class ParameterInfoColumnar extends ParameterInfo {
+    private final Integer startColumn;
+    private final Integer endColumn;
+
+    ParameterInfoColumnar(ParameterInfoColumnar.Builder builder) {
+        super(builder);
+        startColumn = builder.startColumn;
+        endColumn = builder.endColumn;
+    }
+
+    public Integer getStartColumn(){
+        return startColumn;
+    }
+    public Integer getEndColumn(){
+        return endColumn;
+    }
+    @Override
+    protected void validateInternal(CwmsDTOValidator validator) {
+        validator.required(startColumn, "startColumn");
+        validator.required(endColumn, "endColumn");
+    }
+
+    @JsonIgnore
+    @Override
+    public String getParameterInfoString()
+    {
+        return getParameter() +
+                "," +
+                getUnit() +
+                "," +
+                "," +
+                getStartColumn() +
+                "," +
+                getEndColumn();
+    }
+
+    @JsonPOJOBuilder
+    @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
+    public static final class Builder extends ParameterInfo.Builder{
+       private Integer startColumn;
+        private Integer endColumn;
+
+       public ParameterInfoColumnar.Builder withStartColumn(int startColumn){
+            this.startColumn = startColumn;
+            return this;
+        }
+        public ParameterInfoColumnar.Builder withEndColumn(int endColumn){
+            this.endColumn = endColumn;
+            return this;
+        }
+        public ParameterInfo build() {
+            return new ParameterInfoColumnar(this);
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/timeseriesprofile/ParameterInfoIndexed.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/timeseriesprofile/ParameterInfoIndexed.java
new file mode 100644
index 000000000..5c72bd0d2
--- /dev/null
+++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/timeseriesprofile/ParameterInfoIndexed.java
@@ -0,0 +1,64 @@
+package cwms.cda.data.dto.timeseriesprofile;
+
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
+import cwms.cda.data.dto.CwmsDTOValidator;
+import cwms.cda.formatters.Formats;
+import cwms.cda.formatters.annotations.FormattableWith;
+import cwms.cda.formatters.json.JsonV2;
+
+@FormattableWith(contentType = Formats.JSONV2, formatter = JsonV2.class)
+@JsonDeserialize(builder = ParameterInfoIndexed.Builder.class)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
+public final class ParameterInfoIndexed extends ParameterInfo {
+    private final Integer index;
+
+    ParameterInfoIndexed(ParameterInfoIndexed.Builder builder) {
+        super(builder);
+        index = builder.index;
+    }
+
+    public Integer getIndex() {
+        return index;
+    }
+
+    @Override
+    protected void validateInternal(CwmsDTOValidator validator) {
+        validator.required(getIndex(), "index");
+    }
+
+    @JsonIgnore
+    @Override
+    public String getParameterInfoString() {
+        return getParameter() +
+                "," +
+                getUnit() +
+                "," +
+                getIndex() +
+                "," +
+                ",";
+    }
+
+
+    @JsonPOJOBuilder
+    @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
+    public static final class Builder extends ParameterInfo.Builder {
+        private Integer index;
+
+        public ParameterInfoIndexed.Builder withIndex(int index) {
+            this.index = index;
+            return this;
+        }
+
+        public ParameterInfo build() {
+            return new ParameterInfoIndexed(this);
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/timeseriesprofile/ProfileTimeSeries.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/timeseriesprofile/ProfileTimeSeries.java
new file mode 100644
index 000000000..703902162
--- /dev/null
+++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/timeseriesprofile/ProfileTimeSeries.java
@@ -0,0 +1,78 @@
+package cwms.cda.data.dto.timeseriesprofile;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
+import cwms.cda.formatters.Formats;
+import cwms.cda.formatters.annotations.FormattableWith;
+import cwms.cda.formatters.json.JsonV2;
+
+import java.util.List;
+
+@FormattableWith(contentType = Formats.JSONV2, formatter = JsonV2.class)
+@JsonDeserialize(builder = ProfileTimeSeries.Builder.class)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
+public final class ProfileTimeSeries {
+    private final String parameter;
+    private final String unit;
+    private final String timeZone;
+    private final List<TimeValuePair> values;
+
+    ProfileTimeSeries(Builder builder)
+    {
+        parameter = builder.parameter;
+        unit = builder.unit;
+        values = builder.values;
+        timeZone = builder.timeZone;
+    }
+    public String getTimeZone()
+    {
+        return timeZone;
+    }
+    public String getParameter()
+    {
+        return parameter;
+    }
+    public String getUnit()
+    {
+        return unit;
+    }
+    public List<TimeValuePair> getValues()
+    {
+        return values;
+    }
+    @JsonPOJOBuilder
+    @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
+    public static final class Builder {
+        private List<TimeValuePair> values;
+        private String unit;
+        private String parameter;
+        private String timeZone;
+        public Builder withValues(List<TimeValuePair> values) {
+            this.values = values;
+            return this;
+        }
+
+        public Builder withParameter(String parameter) {
+            this.parameter = parameter;
+            return this;
+        }
+
+        public Builder withUnit(String unit) {
+            this.unit = unit;
+            return this;
+        }
+
+        public Builder withTimeZone(String timeZone){
+            this.timeZone = timeZone;
+            return this;
+        }
+        public ProfileTimeSeries build() {
+            return new ProfileTimeSeries(this);
+        }
+    }
+
+}
diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/timeseriesprofile/TimeSeriesProfile.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/timeseriesprofile/TimeSeriesProfile.java
new file mode 100644
index 000000000..5dd954d57
--- /dev/null
+++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/timeseriesprofile/TimeSeriesProfile.java
@@ -0,0 +1,113 @@
+package cwms.cda.data.dto.timeseriesprofile;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
+import cwms.cda.data.dto.CwmsDTOBase;
+import cwms.cda.data.dto.CwmsDTOValidator;
+import cwms.cda.data.dto.CwmsId;
+import cwms.cda.formatters.Formats;
+import cwms.cda.formatters.annotations.FormattableWith;
+import cwms.cda.formatters.json.JsonV2;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+@FormattableWith(contentType = Formats.JSONV2, formatter = JsonV2.class)
+@JsonDeserialize(builder = TimeSeriesProfile.Builder.class)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
+public final class TimeSeriesProfile extends CwmsDTOBase {
+    @Schema(description = "Location ID")
+    private final CwmsId locationId;
+    @Schema(description = "Description")
+    private final String description;
+    @Schema(description = "Parameter List")
+    private final List<String> parameterList;
+    @Schema(description = "Key Parameter")
+    private final String keyParameter;
+    @Schema(description = "Reference TS")
+    private final CwmsId referenceTsId;
+
+    private TimeSeriesProfile(Builder builder) {
+        this.locationId = builder.locationId;
+        this.description = builder.description;
+        this.keyParameter = builder.keyParameter;
+        this.parameterList = builder.parameterList;
+        this.referenceTsId = builder.referenceTsId;
+    }
+
+    public CwmsId getLocationId() {
+        return locationId;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public String getKeyParameter() {
+        return keyParameter;
+    }
+
+    public List<String> getParameterList() {
+        return parameterList;
+    }
+
+    public CwmsId getReferenceTsId() {
+        return referenceTsId;
+    }
+
+    @Override
+    protected void validateInternal(CwmsDTOValidator validator) {
+        super.validateInternal(validator);
+        validator.required(getParameterList(), "parameterList");
+        validator.required(getKeyParameter(), "keyParameter");
+        validator.required(getLocationId(), "locationId");
+    }
+
+    @JsonPOJOBuilder
+    @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
+    public static final class Builder {
+        private final List<String> parameterList = new ArrayList<>();
+        private String keyParameter;
+        private String description;
+        private CwmsId locationId;
+        private CwmsId referenceTsId;
+
+        public TimeSeriesProfile.Builder withLocationId(CwmsId locationId) {
+            this.locationId = locationId;
+            return this;
+        }
+
+        public TimeSeriesProfile.Builder withDescription(String description) {
+            this.description = description;
+            return this;
+        }
+
+        public TimeSeriesProfile.Builder withKeyParameter(String keyParameter) {
+            this.keyParameter = keyParameter;
+            return this;
+        }
+
+        public TimeSeriesProfile.Builder withParameterList(List<String> parameterList) {
+            this.parameterList.clear();
+            if (parameterList != null) {
+                this.parameterList.addAll(parameterList);
+            }
+             return this;
+        }
+
+        public TimeSeriesProfile.Builder withReferenceTsId(CwmsId referenceTsId) {
+            this.referenceTsId = referenceTsId;
+            return this;
+        }
+
+
+        public TimeSeriesProfile build() {
+            return new TimeSeriesProfile(this);
+        }
+    }
+}
diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/timeseriesprofile/TimeSeriesProfileInstance.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/timeseriesprofile/TimeSeriesProfileInstance.java
new file mode 100644
index 000000000..790da2801
--- /dev/null
+++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/timeseriesprofile/TimeSeriesProfileInstance.java
@@ -0,0 +1,115 @@
+package cwms.cda.data.dto.timeseriesprofile;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
+import cwms.cda.api.errors.RequiredFieldException;
+import cwms.cda.data.dto.CwmsDTOBase;
+import cwms.cda.data.dto.CwmsDTOValidator;
+import cwms.cda.formatters.Formats;
+import cwms.cda.formatters.annotations.FormattableWith;
+import cwms.cda.formatters.json.JsonV2;
+
+import java.time.Instant;
+import java.util.List;
+
+@FormattableWith(contentType = Formats.JSONV2, formatter = JsonV2.class)
+@JsonDeserialize(builder = TimeSeriesProfileInstance.Builder.class)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
+public final class TimeSeriesProfileInstance extends CwmsDTOBase {
+    private final TimeSeriesProfile timeSeriesProfile;
+    private final List<ProfileTimeSeries> timeSeriesList;
+    private final String version;
+    private final Instant versionDate;
+    private final Instant firstDate;
+    private final Instant lastDate;
+
+    private TimeSeriesProfileInstance(Builder builder)
+    {
+        timeSeriesList = builder.timeSeriesList;
+        timeSeriesProfile = builder.timeSeriesProfile;
+        version = builder.version;
+        versionDate = builder.versionDate;
+        firstDate = builder.firstDate;
+        lastDate = builder.lastDate;
+    }
+
+    public TimeSeriesProfile getTimeSeriesProfile() {
+        return timeSeriesProfile;
+    }
+
+    public List<ProfileTimeSeries> getTimeSeriesList() {
+        return timeSeriesList;
+    }
+
+    public String getVersion()
+    {
+        return version;
+    }
+    public Instant getVersionDate()
+    {
+        return versionDate;
+    }
+    public Instant getFirstDate()
+    {
+        return firstDate;
+    }
+    public Instant getLastDate()
+    {
+        return lastDate;
+    }
+    @Override
+    protected void validateInternal(CwmsDTOValidator validator){
+        if(timeSeriesProfile==null)
+        {
+            throw new RequiredFieldException("timeSeriesProfile");
+        }
+    }
+    @JsonPOJOBuilder
+    @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
+    public static final class Builder {
+        private List<ProfileTimeSeries> timeSeriesList;
+        private TimeSeriesProfile timeSeriesProfile;
+        private String version;
+        private Instant versionDate;
+        private Instant firstDate;
+        private Instant lastDate;
+
+        public TimeSeriesProfileInstance.Builder withTimeSeriesProfile(TimeSeriesProfile timeSeriesProfile) {
+            this.timeSeriesProfile = timeSeriesProfile;
+            return this;
+        }
+
+        public TimeSeriesProfileInstance.Builder withTimeSeriesList(List<ProfileTimeSeries> timeSeriesList) {
+            this.timeSeriesList = timeSeriesList;
+            return this;
+        }
+
+        public TimeSeriesProfileInstance.Builder withVersion(String version)
+        {
+            this.version = version;
+            return this;
+        }
+        public TimeSeriesProfileInstance.Builder withVersionDate(Instant instant)
+        {
+            this.versionDate = instant;
+            return this;
+        }
+        public TimeSeriesProfileInstance.Builder withFirstDate(Instant instant)
+        {
+            this.firstDate = instant;
+            return this;
+        }
+        public TimeSeriesProfileInstance.Builder withLastDate(Instant instant)
+        {
+            this.lastDate = instant;
+            return this;
+        }
+        public TimeSeriesProfileInstance build() {
+            return new TimeSeriesProfileInstance(this);
+        }
+    }
+}
diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/timeseriesprofile/TimeSeriesProfileParser.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/timeseriesprofile/TimeSeriesProfileParser.java
new file mode 100644
index 000000000..0e21bde83
--- /dev/null
+++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/timeseriesprofile/TimeSeriesProfileParser.java
@@ -0,0 +1,126 @@
+package cwms.cda.data.dto.timeseriesprofile;
+
+import java.util.List;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
+import cwms.cda.data.dto.CwmsDTOBase;
+import cwms.cda.data.dto.CwmsDTOValidator;
+import cwms.cda.data.dto.CwmsId;
+
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
+@JsonSubTypes({@JsonSubTypes.Type(value = TimeSeriesProfileParserIndexed.class, name = "indexed-timeseries-profile-parser"),
+        @JsonSubTypes.Type(value = TimeSeriesProfileParserColumnar.class, name = "columnar-timeseries-profile-parser")
+})
+
+public  class TimeSeriesProfileParser extends CwmsDTOBase {
+    private final CwmsId locationId;
+    private final String keyParameter;
+    private final char recordDelimiter;
+    private final String timeFormat;
+    private final String timeZone;
+    private final List<ParameterInfo> parameterInfoList;
+    private final boolean timeInTwoFields;
+
+    TimeSeriesProfileParser(Builder builder) {
+        locationId = builder.locationId;
+        keyParameter = builder.keyParameter;
+        recordDelimiter = builder.recordDelimiter;
+        timeFormat = builder.timeFormat;
+        timeZone = builder.timeZone;
+        parameterInfoList = builder.parameterInfoList;
+        timeInTwoFields = builder.timeInTwoFields;
+    }
+
+    @Override
+    protected void validateInternal(CwmsDTOValidator validator) {
+        // there must be a key parameter
+        validator.required(getKeyParameter(),"keyParameter");
+     }
+
+
+    public CwmsId getLocationId() {
+        return locationId;
+    }
+
+    public String getKeyParameter() {
+        return keyParameter;
+    }
+
+    public char getRecordDelimiter() {
+        return recordDelimiter;
+    }
+
+     public List<ParameterInfo> getParameterInfoList() {
+        return parameterInfoList;
+    }
+
+    public String getTimeFormat() {
+        return timeFormat;
+    }
+
+    public String getTimeZone() {
+        return timeZone;
+    }
+
+    public boolean getTimeInTwoFields() {
+        return timeInTwoFields;
+    }
+
+
+    @JsonPOJOBuilder
+    @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
+    public static class Builder {
+        private List<ParameterInfo> parameterInfoList;
+        private String keyParameter;
+        private char recordDelimiter;
+        private String timeFormat;
+        private String timeZone;
+        private boolean timeInTwoFields;
+        private CwmsId locationId;
+
+        public TimeSeriesProfileParser.Builder withLocationId(CwmsId locationId) {
+            this.locationId = locationId;
+            return this;
+        }
+
+          public TimeSeriesProfileParser.Builder withKeyParameter(String keyParameter) {
+            this.keyParameter = keyParameter;
+            return this;
+        }
+
+        public TimeSeriesProfileParser.Builder withRecordDelimiter(char delimiter) {
+            this.recordDelimiter = delimiter;
+            return this;
+        }
+
+
+        public TimeSeriesProfileParser.Builder withTimeFormat(String timeFormat) {
+            this.timeFormat = timeFormat;
+            return this;
+        }
+
+        public TimeSeriesProfileParser.Builder withTimeZone(String timeZone) {
+            this.timeZone = timeZone;
+            return this;
+        }
+
+
+        public TimeSeriesProfileParser.Builder withTimeInTwoFields(boolean timeInTwoFields) {
+            this.timeInTwoFields = timeInTwoFields;
+            return this;
+        }
+
+        public TimeSeriesProfileParser.Builder withParameterInfoList(List<ParameterInfo> parameterInfoList) {
+            this.parameterInfoList = parameterInfoList;
+            return this;
+        }
+
+        public TimeSeriesProfileParser build() {
+            return new TimeSeriesProfileParser(this);
+        }
+    }
+
+}
diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/timeseriesprofile/TimeSeriesProfileParserColumnar.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/timeseriesprofile/TimeSeriesProfileParserColumnar.java
new file mode 100644
index 000000000..75a8eef4a
--- /dev/null
+++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/timeseriesprofile/TimeSeriesProfileParserColumnar.java
@@ -0,0 +1,70 @@
+package cwms.cda.data.dto.timeseriesprofile;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
+import cwms.cda.data.dto.CwmsDTOValidator;
+import cwms.cda.formatters.Formats;
+import cwms.cda.formatters.annotations.FormattableWith;
+import cwms.cda.formatters.json.JsonV2;
+
+import java.math.BigInteger;
+
+@FormattableWith(contentType = Formats.JSONV2, formatter = JsonV2.class)
+@JsonDeserialize(builder = TimeSeriesProfileParserColumnar.Builder.class)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
+public final class TimeSeriesProfileParserColumnar extends TimeSeriesProfileParser {
+    private final Integer timeStartColumn;
+    private final Integer timeEndColumn;
+
+    TimeSeriesProfileParserColumnar(Builder builder) {
+        super(builder);
+        timeStartColumn = builder.timeStartColumn;
+        timeEndColumn = builder.timeEndColumn;
+    }
+
+    @Override
+    protected void validateInternal(CwmsDTOValidator validator) {
+        validator.required(getTimeStartColumn(),"timeStartColumn");
+        validator.required(getTimeEndColumn(),"timeEndColumn");
+    }
+
+    public BigInteger getTimeStartColumn() {
+        if (timeStartColumn != null) {
+            return BigInteger.valueOf(timeStartColumn);
+        }
+        return null;
+    }
+    public BigInteger getTimeEndColumn(){
+        if (timeEndColumn != null) {
+            return BigInteger.valueOf(timeEndColumn);
+        }
+        return null;
+    }
+
+    @JsonPOJOBuilder
+    @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
+    public static final class Builder extends TimeSeriesProfileParser.Builder{
+        private Integer timeStartColumn = null;
+        private Integer timeEndColumn = null;
+
+        public TimeSeriesProfileParserColumnar.Builder withTimeStartColumn(int timeStartColumn)
+        {
+            this.timeStartColumn = timeStartColumn;
+            return this;
+        }
+        public TimeSeriesProfileParserColumnar.Builder withTimeEndColumn(int timeEndColumn)
+        {
+            this.timeEndColumn = timeEndColumn;
+            return this;
+        }
+        @Override
+        public TimeSeriesProfileParserColumnar build() {
+            return new TimeSeriesProfileParserColumnar(this);
+        }
+    }
+
+}
diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/timeseriesprofile/TimeSeriesProfileParserIndexed.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/timeseriesprofile/TimeSeriesProfileParserIndexed.java
new file mode 100644
index 000000000..1d49f7b7c
--- /dev/null
+++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/timeseriesprofile/TimeSeriesProfileParserIndexed.java
@@ -0,0 +1,68 @@
+package cwms.cda.data.dto.timeseriesprofile;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
+import cwms.cda.data.dto.CwmsDTOValidator;
+import cwms.cda.formatters.Formats;
+import cwms.cda.formatters.annotations.FormattableWith;
+import cwms.cda.formatters.json.JsonV2;
+
+import java.math.BigInteger;
+
+@FormattableWith(contentType = Formats.JSONV2, formatter = JsonV2.class)
+@JsonDeserialize(builder = TimeSeriesProfileParserIndexed.Builder.class)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
+public  class TimeSeriesProfileParserIndexed extends TimeSeriesProfileParser {
+    private final Character fieldDelimiter;
+    private final Integer timeField;
+
+    TimeSeriesProfileParserIndexed(Builder builder) {
+        super(builder);
+        fieldDelimiter = builder.fieldDelimiter;
+        timeField = builder.timeField;
+    }
+
+    @Override
+    protected void validateInternal(CwmsDTOValidator validator) {
+        validator.required(getFieldDelimiter(),"fieldDelimiter");
+        validator.required(getTimeField(),"timeField");
+     }
+
+
+    public Character getFieldDelimiter() {
+        return fieldDelimiter;
+    }
+
+
+    public BigInteger getTimeField() {
+        return BigInteger.valueOf(timeField);
+    }
+
+    @JsonPOJOBuilder
+    @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
+    public static class Builder extends TimeSeriesProfileParser.Builder{
+        private Character fieldDelimiter = null;
+        private Integer timeField = null;
+
+
+        public TimeSeriesProfileParserIndexed.Builder withFieldDelimiter(char delimiter) {
+            this.fieldDelimiter = delimiter;
+            return this;
+        }
+
+
+        public TimeSeriesProfileParserIndexed.Builder withTimeField(int field) {
+            this.timeField = field;
+            return this;
+        }
+        @Override
+        public TimeSeriesProfileParserIndexed build() {
+            return new TimeSeriesProfileParserIndexed(this);
+        }
+    }
+
+}
diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/timeseriesprofile/TimeValuePair.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/timeseriesprofile/TimeValuePair.java
new file mode 100644
index 000000000..52253db5b
--- /dev/null
+++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/timeseriesprofile/TimeValuePair.java
@@ -0,0 +1,67 @@
+package cwms.cda.data.dto.timeseriesprofile;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
+import cwms.cda.data.dto.CwmsDTOBase;
+import cwms.cda.formatters.Formats;
+import cwms.cda.formatters.annotations.FormattableWith;
+import cwms.cda.formatters.json.JsonV2;
+
+import java.time.Instant;
+
+@FormattableWith(contentType = Formats.JSONV2, formatter = JsonV2.class)
+@JsonDeserialize(builder = TimeValuePair.Builder.class)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
+public final class TimeValuePair extends CwmsDTOBase {
+    private final Instant dateTime;
+    private final double value;
+    private final int quality;
+
+    private TimeValuePair(Builder builder)
+    {
+        dateTime = builder.dateTime;
+        value = builder.value;
+        quality = builder.quality;
+     }
+    public Instant getDateTime()
+    {
+        return dateTime;
+    }
+    public double getValue()
+    {
+        return value;
+    }
+    public int getQuality()
+    {
+        return quality;
+    }
+
+    @JsonPOJOBuilder
+    @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
+    public static final class Builder {
+        private Instant dateTime;
+        private double value;
+        private int quality;
+
+        public Builder withDateTime(Instant dateTime) {
+            this.dateTime = dateTime;
+            return this;
+        }
+
+        public Builder withValue(double value) {
+            this.value = value;
+            return this;
+        }
+        public Builder withQuality(int quality) {
+            this.quality = quality;
+            return this;
+        }
+        public TimeValuePair build() {
+            return new TimeValuePair(this);
+        }
+    }
+}
diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dao/timeseriesprofile/TimeSeriesProfileDaoIT.java b/cwms-data-api/src/test/java/cwms/cda/data/dao/timeseriesprofile/TimeSeriesProfileDaoIT.java
new file mode 100644
index 000000000..83c21a868
--- /dev/null
+++ b/cwms-data-api/src/test/java/cwms/cda/data/dao/timeseriesprofile/TimeSeriesProfileDaoIT.java
@@ -0,0 +1,133 @@
+package cwms.cda.data.dao.timeseriesprofile;
+
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.List;
+
+import cwms.cda.api.DataApiTestIT;
+import cwms.cda.data.dto.CwmsId;
+import cwms.cda.data.dto.timeseriesprofile.TimeSeriesProfile;
+import cwms.cda.data.dto.timeseriesprofile.TimeSeriesProfileTest;
+import fixtures.CwmsDataApiSetupCallback;
+import mil.army.usace.hec.test.database.CwmsDatabaseContainer;
+import org.jooq.DSLContext;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+import static cwms.cda.data.dao.DaoTest.getDslContext;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@Tag("integration")
+class TimeSeriesProfileDaoIT extends DataApiTestIT {
+    @Test
+    void testCopyTimeSeriesProfile() throws SQLException {
+        CwmsDatabaseContainer<?> databaseLink = CwmsDataApiSetupCallback.getDatabaseLink();
+        databaseLink.connection(c -> {
+            String officeId ="LRL";
+            DSLContext context = getDslContext(c, databaseLink.getOfficeId());
+            TimeSeriesProfileDao timeSeriesProfileDao = new TimeSeriesProfileDao(context);
+            TimeSeriesProfile timeSeriesProfile = buildTestTimeSeriesProfile(officeId, "Glensboro", "Depth");
+            timeSeriesProfileDao.storeTimeSeriesProfile(timeSeriesProfile, false);
+            timeSeriesProfileDao.copyTimeSeriesProfile(timeSeriesProfile.getLocationId().getName(), timeSeriesProfile.getKeyParameter(),
+                    "Greensburg", null,
+                    timeSeriesProfile.getLocationId().getOfficeId());
+            TimeSeriesProfile timeSeriesProfileCopied = timeSeriesProfileDao.retrieveTimeSeriesProfile(
+                    "Greensburg",
+                    timeSeriesProfile.getKeyParameter(), timeSeriesProfile.getLocationId().getOfficeId());
+            assertEquals("Greensburg", timeSeriesProfileCopied.getLocationId().getName());
+            assertEquals(timeSeriesProfile.getKeyParameter(), timeSeriesProfileCopied.getKeyParameter());
+            assertEquals(timeSeriesProfile.getParameterList(), timeSeriesProfileCopied.getParameterList());
+
+            timeSeriesProfileDao.deleteTimeSeriesProfile(timeSeriesProfile.getLocationId().getName(), timeSeriesProfile.getKeyParameter(), timeSeriesProfile.getLocationId().getOfficeId());
+            timeSeriesProfileDao.deleteTimeSeriesProfile(timeSeriesProfileCopied.getLocationId().getName(),
+                    timeSeriesProfileCopied.getKeyParameter(), timeSeriesProfileCopied.getLocationId().getOfficeId());
+        }, CwmsDataApiSetupCallback.getWebUser());
+    }
+
+    @Test
+    void testRetrieveTimeSeriesProfile() throws Exception {
+        CwmsDatabaseContainer<?> databaseLink = CwmsDataApiSetupCallback.getDatabaseLink();
+        databaseLink.connection(c -> {
+            DSLContext context = getDslContext(c, databaseLink.getOfficeId());
+            String officeId = "LRL";
+
+            TimeSeriesProfileDao timeSeriesProfileDao = new TimeSeriesProfileDao(context);
+
+            TimeSeriesProfile timeSeriesProfileIn = buildTestTimeSeriesProfile(officeId,"Glensboro","Depth");
+            timeSeriesProfileDao.storeTimeSeriesProfile(timeSeriesProfileIn, false);
+
+            TimeSeriesProfile timeSeriesProfileOut = timeSeriesProfileDao.retrieveTimeSeriesProfile(timeSeriesProfileIn.getLocationId().getName(),
+                    timeSeriesProfileIn.getKeyParameter(), timeSeriesProfileIn.getLocationId().getOfficeId());
+
+            timeSeriesProfileDao.deleteTimeSeriesProfile(timeSeriesProfileIn.getLocationId().getName(),
+                    timeSeriesProfileIn.getKeyParameter(), timeSeriesProfileIn.getLocationId().getOfficeId());
+
+            TimeSeriesProfileTest.testAssertEquals(timeSeriesProfileOut, timeSeriesProfileIn, "");
+
+        }, CwmsDataApiSetupCallback.getWebUser());
+    }
+
+    @Test
+    void testRetrieveCatalog() throws Exception {
+        CwmsDatabaseContainer<?> databaseLink = CwmsDataApiSetupCallback.getDatabaseLink();
+        databaseLink.connection(c -> {
+            DSLContext context = getDslContext(c, databaseLink.getOfficeId());
+            String officeId = "LRL";
+
+            TimeSeriesProfileDao timeSeriesProfileDao = new TimeSeriesProfileDao(context);
+            timeSeriesProfileDao.storeTimeSeriesProfile(buildTestTimeSeriesProfile(officeId,"Glensboro","Depth"), false);
+            timeSeriesProfileDao.storeTimeSeriesProfile(buildTestTimeSeriesProfile(officeId,"Greensburg","Pres"), false);
+
+
+            List<TimeSeriesProfile> timeSeriesProfileListBefore =
+                    timeSeriesProfileDao.catalogTimeSeriesProfiles("*", "*", "*");
+
+            for (TimeSeriesProfile timeSeriesProfile : timeSeriesProfileListBefore) {
+                    timeSeriesProfileDao.deleteTimeSeriesProfile(timeSeriesProfile.getLocationId().getName(), timeSeriesProfile.getKeyParameter(),
+                            timeSeriesProfile.getLocationId().getOfficeId());
+            }
+            List<TimeSeriesProfile> timeSeriesProfileListAfter = timeSeriesProfileDao.catalogTimeSeriesProfiles("*", "*", "*");
+
+            assertEquals(0, timeSeriesProfileListAfter.size());
+            assertEquals(2, timeSeriesProfileListBefore.size());
+        }, CwmsDataApiSetupCallback.getWebUser());
+    }
+
+    @Test
+    void testDeleteTimeSeriesProfile() throws Exception {
+        CwmsDatabaseContainer<?> databaseLink = CwmsDataApiSetupCallback.getDatabaseLink();
+        databaseLink.connection(c -> {
+            DSLContext context = getDslContext(c, databaseLink.getOfficeId());
+            String officeId = "LRL";
+
+            TimeSeriesProfileDao timeSeriesProfileDao = new TimeSeriesProfileDao(context);
+            TimeSeriesProfile timeSeriesProfile = buildTestTimeSeriesProfile(officeId,"Glensboro","Depth");
+            timeSeriesProfileDao.storeTimeSeriesProfile(timeSeriesProfile, false);
+
+            List<TimeSeriesProfile> timeSeriesProfileListBefore =
+                    timeSeriesProfileDao.catalogTimeSeriesProfiles("*", "*", "*");
+
+            timeSeriesProfileDao.deleteTimeSeriesProfile(timeSeriesProfile.getLocationId().getName(), timeSeriesProfile.getKeyParameter(),
+                    timeSeriesProfile.getLocationId().getOfficeId());
+
+            List<TimeSeriesProfile> timeSeriesProfileListAfter =
+                    timeSeriesProfileDao.catalogTimeSeriesProfiles("*", "*", "*");
+
+
+            assertEquals(timeSeriesProfileListBefore.size() - 1, timeSeriesProfileListAfter.size());
+        }, CwmsDataApiSetupCallback.getWebUser());
+    }
+
+    static private TimeSeriesProfile buildTestTimeSeriesProfile(String officeId, String location, String keyParameter) {
+        CwmsId locationId = new CwmsId.Builder().withOfficeId(officeId).withName(location).build();
+        CwmsId refTsId = new CwmsId.Builder().withOfficeId(officeId).withName("Greensburg.Stage.Inst.1Hour.0.USGS-rev").build();
+        return new TimeSeriesProfile.Builder()
+                .withLocationId(locationId)
+                .withKeyParameter(keyParameter)
+                .withParameterList(Arrays.asList("Pres", "Depth"))
+                .withDescription("description")
+                .withReferenceTsId(refTsId)
+                .build();
+
+    }
+}
diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dao/timeseriesprofile/TimeSeriesProfileInstanceDaoIT.java b/cwms-data-api/src/test/java/cwms/cda/data/dao/timeseriesprofile/TimeSeriesProfileInstanceDaoIT.java
new file mode 100644
index 000000000..795e69332
--- /dev/null
+++ b/cwms-data-api/src/test/java/cwms/cda/data/dao/timeseriesprofile/TimeSeriesProfileInstanceDaoIT.java
@@ -0,0 +1,562 @@
+package cwms.cda.data.dao.timeseriesprofile;
+
+import cwms.cda.api.DataApiTestIT;
+import cwms.cda.data.dao.StoreRule;
+import cwms.cda.data.dto.CwmsId;
+import cwms.cda.data.dto.timeseriesprofile.ParameterInfo;
+import cwms.cda.data.dto.timeseriesprofile.ParameterInfoColumnar;
+import cwms.cda.data.dto.timeseriesprofile.ParameterInfoIndexed;
+import cwms.cda.data.dto.timeseriesprofile.ProfileTimeSeries;
+import cwms.cda.data.dto.timeseriesprofile.TimeSeriesProfile;
+import cwms.cda.data.dto.timeseriesprofile.TimeSeriesProfileInstance;
+import cwms.cda.data.dto.timeseriesprofile.TimeSeriesProfileParserColumnar;
+import cwms.cda.data.dto.timeseriesprofile.TimeSeriesProfileParserIndexed;
+import cwms.cda.data.dto.timeseriesprofile.TimeValuePair;
+import fixtures.CwmsDataApiSetupCallback;
+import mil.army.usace.hec.test.database.CwmsDatabaseContainer;
+import org.apache.commons.io.IOUtils;
+import org.jooq.DSLContext;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import usace.cwms.db.dao.ifc.ts.CwmsDbTs;
+import usace.cwms.db.dao.util.services.CwmsDbServiceLookup;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.sql.SQLException;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static cwms.cda.data.dao.DaoTest.getDslContext;
+import static org.junit.jupiter.api.Assertions.*;
+
+@Tag("integration")
+class TimeSeriesProfileInstanceDaoIT extends DataApiTestIT {
+
+    @Test
+    void testStoreTimeSeriesProfileInstanceWithDataBlock() throws SQLException {
+        CwmsDatabaseContainer<?> databaseLink = CwmsDataApiSetupCallback.getDatabaseLink();
+        databaseLink.connection(c -> {
+            String officeId = "LRL";
+            String locationName = "Glensboro";
+            String versionId = "VERSION";
+            String unit = "kPa,m";
+            String[] parameterArray = {"Depth", "Pres"};
+            int[] parameterIndexArray = {7, 8};
+            String[] parameterUnitArray = {"m", "kPa"};
+            Instant versionDate = Instant.parse("2024-07-09T12:00:00.00Z");
+            Instant startTime = Instant.parse("2018-07-09T19:06:20.00Z");
+            Instant endTime = Instant.parse("2025-07-09T19:06:20.00Z");
+            String profileData;
+            InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/data/dto/timeseriesprofile/timeSeriesProfileData.txt");
+            assertNotNull(resource);
+            try {
+                profileData = IOUtils.toString(resource, StandardCharsets.UTF_8);
+             } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+            String timeZone = "UTC";
+            boolean startInclusive = true;
+            boolean endInclusive = true;
+            boolean previous = true;
+            boolean next = true;
+            boolean maxVersion = true;
+            char fieldDelimiter = ',';
+            char recordDelimiter = '\n';
+            int timeField = 1;
+            String timeFormat = "MM/DD/YYYY,HH24:MI:SS";
+
+            DSLContext context = getDslContext(c, databaseLink.getOfficeId());
+
+            // store a time series profile
+            TimeSeriesProfile timeSeriesProfile = buildTestTimeSeriesProfile(officeId, locationName, parameterArray[0], parameterArray[1]);
+            TimeSeriesProfileDao profileDao = new TimeSeriesProfileDao(context);
+            profileDao.storeTimeSeriesProfile(timeSeriesProfile, false);
+
+            // store a time series parser
+            TimeSeriesProfileParserIndexed parser = buildTestTimeSeriesProfileParserIndexed(officeId, locationName, parameterArray, parameterIndexArray, parameterUnitArray, recordDelimiter, fieldDelimiter,
+                    timeFormat, timeZone, timeField);
+            TimeSeriesProfileParserDao timeSeriesProfileParserDao = new TimeSeriesProfileParserDao(context);
+
+             // now store the new one.
+            timeSeriesProfileParserDao.storeTimeSeriesProfileParser(parser, false);
+
+            // create a time series profile instance and test storeTimeSeriesProfileInstance
+            TimeSeriesProfileInstanceDao timeSeriesProfileInstanceDao = new TimeSeriesProfileInstanceDao(context);
+            String storeRule = StoreRule.REPLACE_ALL.toString();
+            timeSeriesProfileInstanceDao.storeTimeSeriesProfileInstance(timeSeriesProfile, profileData, versionDate, versionId, storeRule, false);
+
+            try {
+                // retrieve the time series profile instance we just stored
+                TimeSeriesProfileInstance timeSeriesProfileInstance = retrieveTimeSeriesProfileInstance(officeId, locationName, parameterArray[0], versionId, unit,
+                        startTime, endTime, timeZone, startInclusive, endInclusive, previous, next, versionDate, maxVersion);
+                // cleanup: delete the instance
+                profileDao.deleteTimeSeriesProfile(timeSeriesProfileInstance.getTimeSeriesProfile().getLocationId().getName(), timeSeriesProfileInstance.getTimeSeriesProfile().getKeyParameter(),
+                        timeSeriesProfileInstance.getTimeSeriesProfile().getLocationId().getOfficeId());
+                // check if the instant contains the timeseries we stpred
+                assertEquals(parameterArray.length, timeSeriesProfileInstance.getTimeSeriesList().size());
+            } catch (SQLException e) {
+               throw new RuntimeException(e);
+            }
+        }, CwmsDataApiSetupCallback.getWebUser());
+    }
+    @Test
+    void testStoreTimeSeriesProfileInstanceWithDataBlockColumnar() throws SQLException {
+        CwmsDatabaseContainer<?> databaseLink = CwmsDataApiSetupCallback.getDatabaseLink();
+        databaseLink.connection(c -> {
+            String officeId = "LRL";
+            String locationName = "Glensboro";
+            String versionId = "VERSION";
+            String unit = "kPa,m";
+            String[] parameterArray = {"Depth", "Pres"};
+            int[][] parameterStartEndArray = {{21, 23}, {25, 27}};
+            String[] parameterUnitArray = {"m", "kPa"};
+            Instant versionDate = Instant.parse("2024-07-09T12:00:00.00Z");
+            Instant startTime = Instant.parse("2018-07-09T19:06:20.00Z");
+            Instant endTime = Instant.parse("2025-07-09T19:06:20.00Z");
+            String profileData;
+            InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/data/dto/timeseriesprofile/timeSeriesProfileDataColumnar.txt");
+            assertNotNull(resource);
+            try {
+                profileData = IOUtils.toString(resource, StandardCharsets.UTF_8);
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+            String timeZone = "UTC";
+            boolean startInclusive = true;
+            boolean endInclusive = true;
+            boolean previous = true;
+            boolean next = true;
+            boolean maxVersion = true;
+            char recordDelimiter = '\n';
+            int[] timeStartEnd = {1, 19};
+
+            String timeFormat = "MM/DD/YYYY,HH24:MI:SS";
+
+            DSLContext context = getDslContext(c, databaseLink.getOfficeId());
+
+            // store a time series profile
+            TimeSeriesProfile timeSeriesProfile = buildTestTimeSeriesProfile(officeId, locationName, parameterArray[0], parameterArray[1]);
+            TimeSeriesProfileDao profileDao = new TimeSeriesProfileDao(context);
+            profileDao.storeTimeSeriesProfile(timeSeriesProfile, false);
+
+            // store a time series parser
+            TimeSeriesProfileParserColumnar parser = buildTestTimeSeriesProfileParserColumnar(officeId, locationName, parameterArray, parameterStartEndArray, parameterUnitArray, recordDelimiter,
+                    timeFormat, timeZone, timeStartEnd);
+            TimeSeriesProfileParserDao timeSeriesProfileParserDao = new TimeSeriesProfileParserDao(context);
+            timeSeriesProfileParserDao.storeTimeSeriesProfileParser(parser, false);
+
+            // create a time series profile instance and test storeTimeSeriesProfileInstance
+            TimeSeriesProfileInstanceDao timeSeriesProfileInstanceDao = new TimeSeriesProfileInstanceDao(context);
+            String storeRule = StoreRule.REPLACE_ALL.toString();
+            timeSeriesProfileInstanceDao.storeTimeSeriesProfileInstance(timeSeriesProfile, profileData, versionDate, versionId, storeRule, false);
+
+            try {
+                // retrieve the time series profile instance we just stored
+                TimeSeriesProfileInstance timeSeriesProfileInstance = retrieveTimeSeriesProfileInstance(officeId, locationName, parameterArray[0], versionId, unit,
+                        startTime, endTime, timeZone, startInclusive, endInclusive, previous, next, versionDate, maxVersion);
+                // cleanup: delete the instance
+                profileDao.deleteTimeSeriesProfile(timeSeriesProfileInstance.getTimeSeriesProfile().getLocationId().getName(), timeSeriesProfileInstance.getTimeSeriesProfile().getKeyParameter(),
+                        timeSeriesProfileInstance.getTimeSeriesProfile().getLocationId().getOfficeId());
+
+                // check if the instant contains the timeseries we stpred
+                assertEquals(parameterArray.length, timeSeriesProfileInstance.getTimeSeriesList().size());
+            } catch (SQLException e) {
+                throw new RuntimeException(e);
+            }
+         }, CwmsDataApiSetupCallback.getWebUser());
+    }
+
+
+    @Test
+    void testCatalogTimeSeriesProfileInstances() throws SQLException {
+        Instant versionDate = Instant.parse("2024-07-09T12:00:00.00Z");
+        String officeId = "LRL";
+        String location = "Glensboro";
+        String[] keyParameter = {"Depth", "m"};
+        String[] parameter1 = {"Pres", "psi"};
+        String[] versions = {"VERSION", "VERSION2", "VERSION3"};
+        String officeIdMask = "*";
+        String locationMask = "*";
+        String parameterMask = "*";
+        String versionMask = "*";
+        String timeZone = "UTC";
+        Instant firstDate = Instant.parse("2024-07-09T19:00:11.00Z");
+        Instant[] dateTimeArray = {Instant.parse("2024-07-09T19:00:11.00Z"), Instant.parse("2024-07-09T20:00:22.00Z")};
+        double[] valueArray = {1, 4};
+
+        // store a few timeseries profile instances
+        for (String version : versions) {
+            storeTimeSeriesProfileInstance(officeId, location, keyParameter, parameter1, version, versionDate, dateTimeArray, valueArray, timeZone);
+        }
+        CwmsDatabaseContainer<?> databaseLink = CwmsDataApiSetupCallback.getDatabaseLink();
+
+        databaseLink.connection(c -> {
+            DSLContext context = getDslContext(c, databaseLink.getOfficeId());
+            TimeSeriesProfileInstanceDao timeSeriesProfileInstanceDao = new TimeSeriesProfileInstanceDao(context);
+            // test retrieveTimeSeriesProfileInstances
+            List<TimeSeriesProfileInstance> result = timeSeriesProfileInstanceDao.catalogTimeSeriesProfileInstances(officeIdMask, locationMask, parameterMask,
+                    versionMask);
+
+            // cleanup: delete the time series profile instances we created
+            boolean overrideProtection = false;
+            for (TimeSeriesProfileInstance timeSeriesProfileInstance : result) {
+                timeSeriesProfileInstanceDao.deleteTimeSeriesProfileInstance(timeSeriesProfileInstance.getTimeSeriesProfile().getLocationId(),
+                        timeSeriesProfileInstance.getTimeSeriesProfile().getKeyParameter(), timeSeriesProfileInstance.getVersion(),
+                        firstDate, timeZone, overrideProtection, timeSeriesProfileInstance.getVersionDate());
+                break;
+            }
+            // check if we retrieve all the instances we stored
+            assertEquals(versions.length, result.size(), CwmsDataApiSetupCallback.getWebUser());
+        });
+    }
+
+    @Test
+    void testRetrieveTimeSeriesProfileInstance() throws SQLException {
+        String officeId = "LRL";
+        String versionID = "VERSION";
+        String locationName = "Glensboro";
+        String[] keyParameter = {"Depth", "m"};
+        String[] parameter1 = {"Pres", "psi"};
+        String unit = "bar,m";
+        Instant startTime = Instant.parse("2024-07-09T19:00:11.00Z");
+        Instant endTime = Instant.parse("2025-01-01T19:00:22.00Z");
+        String timeZone = "UTC";
+        String startInclusive = "T";
+        String endInclusive = "T";
+        String previous = "T";
+        String next = "T";
+        String maxVersion = "T";
+        Instant[] dateTimeArray = {Instant.parse("2024-07-09T19:00:11.00Z"), Instant.parse("2024-07-09T20:00:22.00Z")};
+        double[] valueArray = {1, 4};
+        Instant versionDate = Instant.parse("2024-07-09T12:00:00.00Z");
+
+        // store a time series profile instance
+        storeTimeSeriesProfileInstance(officeId, locationName, keyParameter, parameter1, versionID, versionDate, dateTimeArray, valueArray, timeZone);
+        CwmsDatabaseContainer<?> databaseLink = CwmsDataApiSetupCallback.getDatabaseLink();
+        databaseLink.connection(c -> {
+            DSLContext context = getDslContext(c, databaseLink.getOfficeId());
+            TimeSeriesProfileInstanceDao timeSeriesProfileInstanceDao = new TimeSeriesProfileInstanceDao(context);
+            CwmsId location = new CwmsId.Builder()
+                    .withName(locationName)
+                    .withOfficeId(officeId)
+                    .build();
+            // test the retrieveTimeSeriesProfileInstance method
+            TimeSeriesProfileInstance result = timeSeriesProfileInstanceDao.retrieveTimeSeriesProfileInstance(location, keyParameter[0], versionID,
+                    unit, startTime, endTime, timeZone, startInclusive, endInclusive, previous, next, versionDate,
+                    maxVersion);
+
+            // cleanup: delete the timeseries we created
+            timeSeriesProfileInstanceDao.deleteTimeSeriesProfileInstance(result.getTimeSeriesProfile().getLocationId(),
+                        result.getTimeSeriesProfile().getKeyParameter(), result.getVersion(),
+                        startTime, timeZone, false, result.getVersionDate());
+
+            // check if the retrieved timeseries profile instance has the same tineseries as the one we stored
+            assertEquals(2, result.getTimeSeriesList().size());
+            assertEquals(2, result.getTimeSeriesList().get(0).getValues().size());
+
+        }, CwmsDataApiSetupCallback.getWebUser());
+    }
+
+    @Test
+    void testDeleteTimeSeriesProfileInstance() throws SQLException {
+        Instant versionDate = Instant.parse("2024-07-09T12:00:00.00Z");
+        String officeId = "LRL";
+        String locationName = "Glensboro";
+        String[] keyParameter = {"Depth", "m"};
+        String[] parameter1 = {"Pres", "psi"};
+        String unit = "kPa,m";
+        String version = "VERSION";
+        String timeZone = "UTC";
+        Instant startTime = Instant.parse("2018-07-09T19:06:20.00Z");
+        Instant endTime = Instant.parse("2025-07-09T19:06:20.00Z");
+        boolean overrideProtection = false;
+        boolean startInclusive = true;
+        boolean endInclusive = true;
+        boolean previous = true;
+        boolean next = true;
+        boolean maxVersion = true;
+        Instant firstDate = Instant.parse("2024-07-09T19:00:11.00Z");
+        Instant[] dateTimeArray = {Instant.parse("2024-07-09T19:00:11.00Z"), Instant.parse("2024-07-09T20:00:22.00Z")};
+        double[] valueArray = {3, 5};
+
+        // store a time series profile instance
+        storeTimeSeriesProfileInstance(officeId, locationName, keyParameter, parameter1, version, versionDate, dateTimeArray, valueArray, timeZone);
+        CwmsDatabaseContainer<?> databaseLink = CwmsDataApiSetupCallback.getDatabaseLink();
+        databaseLink.connection(c -> {
+            DSLContext context = getDslContext(c, databaseLink.getOfficeId());
+            TimeSeriesProfileInstanceDao timeSeriesProfileInstanceDao = new TimeSeriesProfileInstanceDao(context);
+
+            // retrieve the instance make sure it exists
+            TimeSeriesProfileInstance timeSeriesProfileInstance;
+            try {
+                timeSeriesProfileInstance = retrieveTimeSeriesProfileInstance(officeId, locationName, keyParameter[0], version, unit,
+                        startTime, endTime, timeZone, startInclusive, endInclusive, previous, next, versionDate, maxVersion);
+            } catch (SQLException e) {
+                throw new RuntimeException(e);
+            }
+            // instance exists?
+            assertNotNull(timeSeriesProfileInstance);
+
+            CwmsId location = new CwmsId.Builder()
+                    .withName(locationName)
+                    .withOfficeId(officeId)
+                    .build();
+
+            //  testing delete
+            timeSeriesProfileInstanceDao.deleteTimeSeriesProfileInstance(location, keyParameter[0], version,
+                    firstDate, timeZone, overrideProtection, versionDate);
+
+            // check if instance was deleted
+            try {
+                timeSeriesProfileInstance = retrieveTimeSeriesProfileInstance(officeId, locationName, keyParameter[0], version, unit,
+                        startTime, endTime, timeZone, startInclusive, endInclusive, previous, next, versionDate, maxVersion);
+            } catch (SQLException e) {
+                throw(new RuntimeException(e));
+            }
+            // instance does not exist anymore
+            assertNull(timeSeriesProfileInstance);
+//
+//            // cleanup the timeseries
+//            try {
+//                CwmsDbTs tsDao = CwmsDbServiceLookup.buildCwmsDb(CwmsDbTs.class, c);
+//                tsDao.deleteAll(c, officeId, locationName + "." + keyParameter[0] + ".Inst.0.0." + version);
+//                tsDao.deleteAll(c, officeId, locationName + "." + parameter1[0] + ".Inst.0.0." + version);
+//            } catch (SQLException e) {
+//                throw(new RuntimeException(e));
+//            }
+        }, CwmsDataApiSetupCallback.getWebUser());
+    }
+
+    @Test
+    void testStoreTimeSeriesProfileInstance() throws SQLException {
+        String versionId = "VERSION";
+        String officeId = "LRL";
+        String locationName = "Glensboro";
+        String[] parameterArray = {"Depth", "Pres"};
+        String[] parameterUnitArray = {"m", "bar"};
+        int[] parameterIndexArray = {5, 6};
+        String[] keyParameter = {parameterArray[0], parameterUnitArray[0]};
+        String[] parameter1 = {parameterArray[1], parameterUnitArray[1]};
+        String unit = "kPa,m";
+        Instant versionDate = Instant.parse("2024-07-09T12:00:00.00Z");
+        String timeZone = "UTC";
+        Instant startTime = Instant.parse("2018-07-09T19:06:20.00Z");
+        Instant endTime = Instant.parse("2025-07-09T19:06:20.00Z");
+        Instant firstDate = Instant.parse("2024-07-09T19:00:11.00Z");
+        boolean startInclusive = true;
+        boolean endInclusive = true;
+        boolean previous = true;
+        boolean next = true;
+        boolean maxVersion = true;
+        Instant[] dateTimeArray = {Instant.parse("2024-07-09T19:00:11.00Z"), Instant.parse("2024-07-09T20:00:22.00Z")};
+        double[] valueArray = {1.0, 5.0};
+        char fieldDelimiter = ',';
+        char recordDelimiter = '\n';
+        int timeField = 1;
+        String timeFormat = "MM/DD/YYYY,HH24:MI:SS";
+        CwmsDatabaseContainer<?> databaseLink = CwmsDataApiSetupCallback.getDatabaseLink();
+        databaseLink.connection(c -> {
+            DSLContext context = getDslContext(c, databaseLink.getOfficeId());
+            TimeSeriesProfileInstance timeSeriesProfileInstance;
+
+
+            // store a profile
+            TimeSeriesProfile timeSeriesProfile = buildTestTimeSeriesProfile(officeId, locationName, keyParameter[0], parameter1[0]);
+            TimeSeriesProfileDao profileDao = new TimeSeriesProfileDao(context);
+            profileDao.storeTimeSeriesProfile(timeSeriesProfile, false);
+
+            // store a parser
+            TimeSeriesProfileParserIndexed parser = buildTestTimeSeriesProfileParserIndexed(officeId, locationName, parameterArray, parameterIndexArray, parameterUnitArray, recordDelimiter, fieldDelimiter,
+                    timeFormat, timeZone, timeField);
+            TimeSeriesProfileParserDao timeSeriesProfileParserDao = new TimeSeriesProfileParserDao(context);
+            timeSeriesProfileParserDao.storeTimeSeriesProfileParser(parser, false);
+
+            String storeRule = StoreRule.REPLACE_ALL.toString();
+            /// create an instance for parameter Depth
+            TimeSeriesProfileInstanceDao timeSeriesProfileInstanceDao = new TimeSeriesProfileInstanceDao(context);
+            TimeSeriesProfileInstance timeseriesProfileInstance = buildTestTimeSeriesProfileInstance(officeId, locationName, keyParameter, parameter1, versionId,
+                    dateTimeArray, valueArray, timeZone, versionDate);
+            // test storeTImeSeriesProfileInstance method
+            timeSeriesProfileInstanceDao.storeTimeSeriesProfileInstance(timeseriesProfileInstance, versionId, versionDate, storeRule, null);
+             // check is the timeseries profile instance can be retrieved
+            try {
+                timeSeriesProfileInstance = retrieveTimeSeriesProfileInstance(officeId, locationName, keyParameter[0], versionId, unit,
+                        startTime, endTime, timeZone, startInclusive, endInclusive, previous, next, versionDate, maxVersion);
+            }
+            catch (SQLException e)
+            {
+                throw new RuntimeException(e);
+            }
+            // instance exists?
+
+            // cleanup delete the timeseries profile instance and its timeseries
+            boolean overrideProtection = false;
+            timeSeriesProfileInstanceDao.deleteTimeSeriesProfileInstance(timeseriesProfileInstance.getTimeSeriesProfile().getLocationId(),
+                    timeSeriesProfile.getKeyParameter(), versionId,
+                    firstDate, timeZone, overrideProtection, versionDate);
+
+            try {
+                  CwmsDbTs tsDao = CwmsDbServiceLookup.buildCwmsDb(CwmsDbTs.class, c);
+                tsDao.deleteAll(c, officeId, locationName + "." + keyParameter[0] + ".Inst.0.0." + versionId);
+                tsDao.deleteAll(c, officeId, locationName + "." + parameter1[0] + ".Inst.0.0." + versionId);
+            } catch (SQLException e) {
+                  throw new RuntimeException(e);
+            }
+
+             assertNotNull(timeSeriesProfileInstance);
+        }, CwmsDataApiSetupCallback.getWebUser());
+    }
+
+    private TimeSeriesProfileInstance retrieveTimeSeriesProfileInstance(String officeId, String locationName, String keyParameter, String version, String unit,
+            Instant startTime, Instant endTime, String timeZone, boolean startInclusive, boolean endInclusive, boolean previous, boolean next,
+            Instant versionDate, boolean maxVersion) throws SQLException {
+        final TimeSeriesProfileInstance[] result = {null};
+        CwmsDatabaseContainer<?> databaseLink = CwmsDataApiSetupCallback.getDatabaseLink();
+        databaseLink.connection(c -> {
+            DSLContext context = getDslContext(c, databaseLink.getOfficeId());
+            TimeSeriesProfileInstanceDao timeSeriesProfileInstanceDao = new TimeSeriesProfileInstanceDao(context);
+            CwmsId location = new CwmsId.Builder()
+                    .withOfficeId(officeId)
+                    .withName(locationName)
+                    .build();
+            try {
+                result[0] = timeSeriesProfileInstanceDao.retrieveTimeSeriesProfileInstance(location, keyParameter, version,
+                        unit, startTime, endTime, timeZone, startInclusive ? "T" : "F", endInclusive ? "T" : "F", previous ? "T" : "F", next ? "T" : "F", versionDate,
+                        maxVersion ? "T" : "F");
+            }
+            catch(cwms.cda.api.errors.NotFoundException ex)
+            {
+                // return null for not found
+            }
+        }, CwmsDataApiSetupCallback.getWebUser());
+        return result[0];
+    }
+
+    private void storeTimeSeriesProfileInstance(String officeId, String location, String[] keyParameter, String[] parameter1, String version, Instant versionInstant, Instant[] dateTimeArray, double[] valueArray,
+            String timeZone) throws SQLException {
+        CwmsDatabaseContainer<?> databaseLink = CwmsDataApiSetupCallback.getDatabaseLink();
+        databaseLink.connection(c -> {
+            DSLContext context = getDslContext(c, databaseLink.getOfficeId());
+            TimeSeriesProfile timeSeriesProfile = buildTestTimeSeriesProfile(officeId, location, keyParameter[0], parameter1[0]);
+            TimeSeriesProfileDao profileDao = new TimeSeriesProfileDao(context);
+            profileDao.storeTimeSeriesProfile(timeSeriesProfile, false);
+
+            /// create an instance for parameter Depth
+            TimeSeriesProfileInstanceDao timeSeriesProfileInstanceDao = new TimeSeriesProfileInstanceDao(context);
+            TimeSeriesProfileInstance timeseriesProfileInstance = buildTestTimeSeriesProfileInstance(officeId, location, keyParameter, parameter1, version, dateTimeArray, valueArray, timeZone, versionInstant);
+            String storeRule = StoreRule.REPLACE_ALL.toString();
+
+            timeSeriesProfileInstanceDao.storeTimeSeriesProfileInstance(timeseriesProfileInstance, version, versionInstant, storeRule, "F");
+
+        }, CwmsDataApiSetupCallback.getWebUser());
+    }
+
+    private static TimeSeriesProfileInstance buildTestTimeSeriesProfileInstance(String officeId, String locationName, String[] keyParameterUnit, String[] parameterUnit1, String version,
+            Instant[] dateTimeArray, double[] valueArray, String timeZone, Instant versionInstant) {
+
+        TimeSeriesProfile timeSeriesProfile = buildTestTimeSeriesProfile(officeId, locationName, keyParameterUnit[0], parameterUnit1[0]);
+
+
+        List<TimeValuePair> timeValuePairList = new ArrayList<>();
+        for (int i = 0; i < dateTimeArray.length; i++) {
+            TimeValuePair timeValuePair = new TimeValuePair.Builder()
+                    .withValue(valueArray[i])
+                    .withDateTime(dateTimeArray[i])
+                    .build();
+            timeValuePairList.add(timeValuePair);
+        }
+
+        ProfileTimeSeries profileTimeSeries = new ProfileTimeSeries.Builder()
+                .withParameter(keyParameterUnit[0])
+                .withUnit(keyParameterUnit[1])
+                .withTimeZone(timeZone)
+                .withValues(timeValuePairList)
+                .build();
+
+        List<ProfileTimeSeries> timeSeriesList = new ArrayList<>();
+        timeSeriesList.add(profileTimeSeries);
+        profileTimeSeries = new ProfileTimeSeries.Builder()
+                .withParameter(parameterUnit1[0])
+                .withUnit(parameterUnit1[1])
+                .withTimeZone(timeZone)
+                .withValues(timeValuePairList)
+                .build();
+
+        timeSeriesList.add(profileTimeSeries);
+        return new TimeSeriesProfileInstance.Builder()
+                .withTimeSeriesProfile(timeSeriesProfile)
+                .withTimeSeriesList(timeSeriesList)
+                .withVersion(version)
+                .withVersionDate(versionInstant)
+                .build();
+    }
+
+    static private TimeSeriesProfile buildTestTimeSeriesProfile(String officeId, String locationName, String keyParameter, String parameter1) {
+        CwmsId locationId = new CwmsId.Builder().withOfficeId(officeId).withName(locationName).build();
+        return new TimeSeriesProfile.Builder()
+                .withLocationId(locationId)
+                .withKeyParameter(keyParameter)
+                .withParameterList(Arrays.asList(parameter1, keyParameter))
+                .withDescription("description")
+                .build();
+
+    }
+    private TimeSeriesProfileParserColumnar buildTestTimeSeriesProfileParserColumnar(String officeId, String location, String[] parameterArray, int[][] parameterStartEndArray, String[] parameterUnitArray, char recordDelimiter, String timeFormat, String timeZone, int[] timeStartEnd) {
+        List<ParameterInfo> parameterInfoList = new ArrayList<>();
+        for (int i = 0; i < parameterArray.length; i++) {
+            parameterInfoList.add(new ParameterInfoColumnar.Builder()
+                    .withStartColumn(parameterStartEndArray[i][0])
+                    .withEndColumn(parameterStartEndArray[i][1])
+                    .withUnit(parameterUnitArray[i])
+                    .withParameter(parameterArray[i])
+                    .build());
+        }
+
+        CwmsId locationId = new CwmsId.Builder().withOfficeId(officeId).withName(location).build();
+        return (TimeSeriesProfileParserColumnar)
+
+                new TimeSeriesProfileParserColumnar.Builder()
+                        .withTimeStartColumn(timeStartEnd[0])
+                        .withTimeEndColumn(timeStartEnd[1])
+                        .withLocationId(locationId)
+                        .withKeyParameter(parameterArray[0])
+                        .withRecordDelimiter(recordDelimiter)
+                        .withTimeFormat(timeFormat)
+                        .withTimeZone(timeZone)
+
+                        .withTimeInTwoFields(false)
+                        .withParameterInfoList(parameterInfoList)
+                        .build();
+    }
+
+    static private TimeSeriesProfileParserIndexed buildTestTimeSeriesProfileParserIndexed(String officeId, String location, String[] parameterArray, int[] parameterIndexArray, String[] parameterUnitArray,
+            char recordDelimiter, char fieldDelimiter, String timeFormat, String timeZone, int timeField) {
+        List<ParameterInfo> parameterInfoList = new ArrayList<>();
+        for (int i = 0; i < parameterArray.length; i++) {
+            parameterInfoList.add(new ParameterInfoIndexed.Builder()
+                    .withIndex(parameterIndexArray[i])
+                    .withParameter(parameterArray[i])
+                    .withUnit(parameterUnitArray[i])
+                    .build());
+        }
+
+        CwmsId locationId = new CwmsId.Builder().withOfficeId(officeId).withName(location).build();
+        return (TimeSeriesProfileParserIndexed)
+
+                new TimeSeriesProfileParserIndexed.Builder()
+                        .withFieldDelimiter(fieldDelimiter)
+                        .withTimeField(timeField)
+                        .withLocationId(locationId)
+                        .withKeyParameter(parameterArray[0])
+                        .withRecordDelimiter(recordDelimiter)
+                        .withTimeFormat(timeFormat)
+                        .withTimeZone(timeZone)
+                        .withTimeInTwoFields(true)
+                        .withParameterInfoList(parameterInfoList)
+                        .build();
+    }
+}
diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dao/timeseriesprofile/TimeSeriesProfileParserDaoIT.java b/cwms-data-api/src/test/java/cwms/cda/data/dao/timeseriesprofile/TimeSeriesProfileParserDaoIT.java
new file mode 100644
index 000000000..e77acc576
--- /dev/null
+++ b/cwms-data-api/src/test/java/cwms/cda/data/dao/timeseriesprofile/TimeSeriesProfileParserDaoIT.java
@@ -0,0 +1,252 @@
+package cwms.cda.data.dao.timeseriesprofile;
+
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import cwms.cda.api.DataApiTestIT;
+import cwms.cda.data.dto.CwmsId;
+import cwms.cda.data.dto.timeseriesprofile.ParameterInfo;
+import cwms.cda.data.dto.timeseriesprofile.ParameterInfoIndexed;
+import cwms.cda.data.dto.timeseriesprofile.TimeSeriesProfile;
+import cwms.cda.data.dto.timeseriesprofile.TimeSeriesProfileParser;
+import cwms.cda.data.dto.timeseriesprofile.TimeSeriesProfileParserIndexed;
+import fixtures.CwmsDataApiSetupCallback;
+import mil.army.usace.hec.test.database.CwmsDatabaseContainer;
+import org.jooq.DSLContext;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+import static cwms.cda.data.dao.DaoTest.getDslContext;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@Tag("integration")
+final class TimeSeriesProfileParserDaoIT extends DataApiTestIT {
+
+
+    @Test
+    void testStoreAndRetrieve() throws Exception {
+        String officeId = "LRL";
+        String locationName = "Glensboro";
+        String locationName1 = "Greensburg";
+        String[] parameter = {"Depth", "Temp-Water"};
+        CwmsDatabaseContainer<?> databaseLink = CwmsDataApiSetupCallback.getDatabaseLink();
+        databaseLink.connection(c -> {
+            DSLContext context = getDslContext(c, databaseLink.getOfficeId());
+
+            TimeSeriesProfileDao timeSeriesProfileDao = new TimeSeriesProfileDao(context);
+
+            TimeSeriesProfile timeSeriesProfile = buildTestTimeSeriesProfile(officeId, locationName, parameter[0], parameter);
+            timeSeriesProfileDao.storeTimeSeriesProfile(timeSeriesProfile, false);
+
+            timeSeriesProfile = buildTestTimeSeriesProfile(officeId, locationName1, parameter[0], parameter);
+            timeSeriesProfileDao.storeTimeSeriesProfile(timeSeriesProfile, false);
+
+            TimeSeriesProfileParserDao timeSeriesProfileParserDao = new TimeSeriesProfileParserDao(context);
+            TimeSeriesProfileParserIndexed timeSeriesProfileParser = buildTestTimeSeriesProfileParserIndexed(officeId, locationName, parameter[0]);
+            timeSeriesProfileParserDao.storeTimeSeriesProfileParser(timeSeriesProfileParser, false);
+
+            TimeSeriesProfileParser retrieved = timeSeriesProfileParserDao.retrieveTimeSeriesProfileParser(timeSeriesProfileParser.getLocationId().getName(),
+                    timeSeriesProfileParser.getKeyParameter(), timeSeriesProfileParser.getLocationId().getOfficeId());
+            assertEquals(timeSeriesProfileParser.getLocationId().getName(), retrieved.getLocationId().getName());
+            assertEquals(timeSeriesProfileParser.getLocationId().getOfficeId(), retrieved.getLocationId().getOfficeId());
+            assertEquals(timeSeriesProfileParser.getTimeFormat(), retrieved.getTimeFormat());
+            assertEquals(timeSeriesProfileParser.getKeyParameter(), retrieved.getKeyParameter());
+
+            timeSeriesProfileParserDao.deleteTimeSeriesProfileParser(timeSeriesProfileParser.getLocationId().getName(),
+                    timeSeriesProfileParser.getKeyParameter(), timeSeriesProfileParser.getLocationId().getOfficeId());
+
+            timeSeriesProfileDao.deleteTimeSeriesProfile(timeSeriesProfile.getLocationId().getName(), timeSeriesProfile.getKeyParameter(),
+                    timeSeriesProfile.getLocationId().getOfficeId());
+        }, CwmsDataApiSetupCallback.getWebUser());
+    }
+
+    @Test
+    void testStoreAndDelete() throws SQLException {
+        String officeId = "LRL";
+        String locationName = "Glensboro";
+        String[] parameter = { "Depth", "Temp-Water"};
+        CwmsDatabaseContainer<?> databaseLink = CwmsDataApiSetupCallback.getDatabaseLink();
+        databaseLink.connection(c -> {
+            DSLContext context = getDslContext(c, databaseLink.getOfficeId());
+
+            TimeSeriesProfileDao timeSeriesProfileDao = new TimeSeriesProfileDao(context);
+
+            TimeSeriesProfile timeSeriesProfile = buildTestTimeSeriesProfile(officeId, locationName, parameter[0], parameter);
+            timeSeriesProfileDao.storeTimeSeriesProfile(timeSeriesProfile, false);
+
+            TimeSeriesProfileParserDao timeSeriesProfileParserDao = new TimeSeriesProfileParserDao(context);
+            TimeSeriesProfileParserIndexed timeSeriesProfileParser = buildTestTimeSeriesProfileParserIndexed(officeId, locationName, parameter[0]);
+            timeSeriesProfileParserDao.storeTimeSeriesProfileParser( timeSeriesProfileParser, false);
+
+            timeSeriesProfileParserDao.deleteTimeSeriesProfileParser(timeSeriesProfileParser.getLocationId().getName(),
+                    timeSeriesProfileParser.getKeyParameter(), timeSeriesProfileParser.getLocationId().getOfficeId());
+
+            assertThrows(Exception.class, () -> timeSeriesProfileParserDao.retrieveTimeSeriesProfileParser(
+                    timeSeriesProfileParser.getLocationId().getName(),
+                    timeSeriesProfileParser.getKeyParameter(), timeSeriesProfileParser.getLocationId().getOfficeId()));
+
+            timeSeriesProfileDao.deleteTimeSeriesProfile(timeSeriesProfile.getLocationId().getName(), timeSeriesProfile.getKeyParameter(),
+                    timeSeriesProfile.getLocationId().getOfficeId());
+        }, CwmsDataApiSetupCallback.getWebUser());
+    }
+
+    @Test
+    void testCatalogTimeSeriesProfileInclusive() throws SQLException {
+        CwmsDatabaseContainer<?> databaseLink = CwmsDataApiSetupCallback.getDatabaseLink();
+        String officeId = "LRL";
+        String locationName = "Glensboro";
+        String[] parameters =  {"Depth", "Temp-Water"};
+        databaseLink.connection(c -> {
+            DSLContext context = getDslContext(c, databaseLink.getOfficeId());
+
+            TimeSeriesProfileDao timeSeriesProfileDao = new TimeSeriesProfileDao(context);
+
+            List<TimeSeriesProfile> timeSeriesProfileList = new ArrayList<>();
+            for(String parameter : parameters) {
+                TimeSeriesProfile timeSeriesProfile = buildTestTimeSeriesProfile(officeId, locationName, parameter, parameters);
+                timeSeriesProfileDao.storeTimeSeriesProfile(timeSeriesProfile, false);
+                timeSeriesProfileList.add(timeSeriesProfile);
+            }
+            TimeSeriesProfileParserDao timeSeriesProfileParserDao = new TimeSeriesProfileParserDao(context);
+
+
+            for(String parameter : parameters) {
+                TimeSeriesProfileParserIndexed timeSeriesProfileParser = buildTestTimeSeriesProfileParserIndexed(officeId, locationName, parameter);
+                timeSeriesProfileParserDao.storeTimeSeriesProfileParser(timeSeriesProfileParser, false);
+            }
+
+            List<TimeSeriesProfileParser> profileParserList = timeSeriesProfileParserDao.catalogTimeSeriesProfileParsers("*", "*", "*",true);
+            for (TimeSeriesProfileParser profileParser : profileParserList) {
+                timeSeriesProfileParserDao.deleteTimeSeriesProfileParser(profileParser.getLocationId().getName(), profileParser.getKeyParameter(), profileParser.getLocationId().getOfficeId());
+            }
+
+            for(TimeSeriesProfile timeSeriesProfile : timeSeriesProfileList) {
+                timeSeriesProfileDao.deleteTimeSeriesProfile(timeSeriesProfile.getLocationId().getName(), timeSeriesProfile.getKeyParameter(),
+                        timeSeriesProfile.getLocationId().getOfficeId());
+            }
+
+            assertEquals(2, profileParserList.size());
+            assertEquals(2, profileParserList.get(0).getParameterInfoList().size() );
+            assertEquals(2, profileParserList.get(1).getParameterInfoList().size() );
+        }, CwmsDataApiSetupCallback.getWebUser());
+
+    }
+    @Test
+    void testStoreAndRetrieveMultiple() throws SQLException {
+        CwmsDatabaseContainer<?> databaseLink = CwmsDataApiSetupCallback.getDatabaseLink();
+        String officeId = "LRL";
+        String locationName = "Glensboro";
+        String[] parameters =  {"Depth", "Temp-Water"};
+        databaseLink.connection(c -> {
+            DSLContext context = getDslContext(c, databaseLink.getOfficeId());
+
+            TimeSeriesProfileDao timeSeriesProfileDao = new TimeSeriesProfileDao(context);
+
+            List<TimeSeriesProfile> timeSeriesProfileList = new ArrayList<>();
+            for(String parameter : parameters) {
+                TimeSeriesProfile timeSeriesProfile = buildTestTimeSeriesProfile(officeId, locationName, parameter, parameters);
+                timeSeriesProfileDao.storeTimeSeriesProfile(timeSeriesProfile, false);
+                timeSeriesProfileList.add(timeSeriesProfile);
+            }
+            TimeSeriesProfileParserDao timeSeriesProfileParserDao = new TimeSeriesProfileParserDao(context);
+
+
+            for(String parameter : parameters) {
+                TimeSeriesProfileParserIndexed timeSeriesProfileParser = buildTestTimeSeriesProfileParserIndexed(officeId, locationName, parameter);
+                timeSeriesProfileParserDao.storeTimeSeriesProfileParser(timeSeriesProfileParser, false);
+            }
+
+            List<TimeSeriesProfileParser> profileParserList = timeSeriesProfileParserDao.catalogTimeSeriesProfileParsers("*", "*", "*");
+            for (TimeSeriesProfileParser profileParser : profileParserList) {
+                timeSeriesProfileParserDao.deleteTimeSeriesProfileParser(profileParser.getLocationId().getName(), profileParser.getKeyParameter(), profileParser.getLocationId().getOfficeId());
+            }
+
+            for(TimeSeriesProfile timeSeriesProfile : timeSeriesProfileList) {
+                timeSeriesProfileDao.deleteTimeSeriesProfile(timeSeriesProfile.getLocationId().getName(), timeSeriesProfile.getKeyParameter(),
+                        timeSeriesProfile.getLocationId().getOfficeId());
+            }
+
+            assertEquals(2, profileParserList.size());
+        }, CwmsDataApiSetupCallback.getWebUser());
+    }
+
+    @Test
+    void testStoreAndCopy() throws SQLException {
+        String officeId = "LRL";
+        String locationName = "Glensboro";
+        String locationName1 = "Greensburg";
+        String[] parameter = {"Depth", "Temp-Water"};
+        CwmsDatabaseContainer<?> databaseLink = CwmsDataApiSetupCallback.getDatabaseLink();
+        databaseLink.connection(c -> {
+            DSLContext context = getDslContext(c, databaseLink.getOfficeId());
+
+            TimeSeriesProfileDao timeSeriesProfileDao = new TimeSeriesProfileDao(context);
+
+            TimeSeriesProfile timeSeriesProfile = buildTestTimeSeriesProfile(officeId, locationName, parameter[0], parameter);
+            timeSeriesProfileDao.storeTimeSeriesProfile(timeSeriesProfile, false);
+
+            timeSeriesProfile = buildTestTimeSeriesProfile(officeId, locationName1, parameter[0], parameter);
+            timeSeriesProfileDao.storeTimeSeriesProfile(timeSeriesProfile, false);
+
+            TimeSeriesProfileParserDao timeSeriesProfileParserDao = new TimeSeriesProfileParserDao(context);
+            TimeSeriesProfileParserIndexed timeSeriesProfileParser = buildTestTimeSeriesProfileParserIndexed(officeId, locationName, parameter[0]);
+            timeSeriesProfileParserDao.storeTimeSeriesProfileParser( timeSeriesProfileParser, false);
+
+            TimeSeriesProfileParser retrieved = timeSeriesProfileParserDao.retrieveTimeSeriesProfileParser(timeSeriesProfileParser.getLocationId().getName(),
+                    timeSeriesProfileParser.getKeyParameter(), timeSeriesProfileParser.getLocationId().getOfficeId());
+
+            timeSeriesProfileParserDao.copyTimeSeriesProfileParser(timeSeriesProfileParser.getLocationId().getName(),
+                    timeSeriesProfileParser.getKeyParameter(), timeSeriesProfileParser.getLocationId().getOfficeId(), locationName1);
+
+            TimeSeriesProfileParser retrieved1 = timeSeriesProfileParserDao.retrieveTimeSeriesProfileParser(locationName1,
+                    timeSeriesProfileParser.getKeyParameter(), timeSeriesProfileParser.getLocationId().getOfficeId());
+            assertEquals(timeSeriesProfileParser.getKeyParameter(), retrieved.getKeyParameter());
+            assertEquals(locationName1, retrieved1.getLocationId().getName());
+            assertEquals(timeSeriesProfileParser.getTimeFormat(), retrieved1.getTimeFormat());
+
+        }, CwmsDataApiSetupCallback.getWebUser());
+    }
+
+    private static TimeSeriesProfile buildTestTimeSeriesProfile(String officeId, String location, String keyParameter, String[] parameters) {
+        CwmsId locationId = new CwmsId.Builder().withOfficeId(officeId).withName(location).build();
+        return new TimeSeriesProfile.Builder()
+                .withLocationId(locationId)
+                .withKeyParameter(keyParameter)
+                .withParameterList(Arrays.asList(parameters))
+                .build();
+
+    }
+
+    static private TimeSeriesProfileParserIndexed buildTestTimeSeriesProfileParserIndexed(String officeId, String location, String keyParameter) {
+        List<ParameterInfo> parameterInfoList = new ArrayList<>();
+        parameterInfoList.add(new ParameterInfoIndexed.Builder()
+                .withIndex(6)
+                .withParameter("Depth")
+                .withUnit("m")
+                .build());
+        parameterInfoList.add(new ParameterInfoIndexed.Builder()
+                .withIndex(5)
+                .withParameter("Pres")
+                .withUnit("bar")
+                .build());
+         CwmsId locationId = new CwmsId.Builder().withOfficeId(officeId).withName(location).build();
+        return
+                (TimeSeriesProfileParserIndexed)
+                new TimeSeriesProfileParserIndexed.Builder()
+                        .withFieldDelimiter(',')
+                        .withTimeField(1)
+                        .withLocationId(locationId)
+                        .withKeyParameter(keyParameter)
+                        .withRecordDelimiter((char) 10)
+                         .withTimeFormat("MM/DD/YYYY,HH24:MI:SS")
+                        .withTimeZone("UTC")
+                         .withTimeInTwoFields(true)
+                        .withParameterInfoList(parameterInfoList)
+                        .build();
+    }
+
+
+}
diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dto/timeseriesprofile/ParameterInfoTest.java b/cwms-data-api/src/test/java/cwms/cda/data/dto/timeseriesprofile/ParameterInfoTest.java
new file mode 100644
index 000000000..7d16ce7e0
--- /dev/null
+++ b/cwms-data-api/src/test/java/cwms/cda/data/dto/timeseriesprofile/ParameterInfoTest.java
@@ -0,0 +1,81 @@
+package cwms.cda.data.dto.timeseriesprofile;
+
+import cwms.cda.formatters.ContentType;
+import cwms.cda.formatters.Formats;
+import org.apache.commons.io.IOUtils;
+import org.junit.jupiter.api.Test;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+class ParameterInfoTest {
+    @Test
+    void testParameterInfoColumnarRoundTrip() {
+        ParameterInfo parameterInfo = buildParameterInfoColumnar();
+        ContentType contentType = Formats.parseHeader(Formats.JSONV2, ParameterInfoColumnar.class);
+        String serialized = Formats.format(contentType, parameterInfo);
+        ParameterInfoColumnar deserialized = Formats.parseContent(Formats.parseHeader(Formats.JSONV2, ParameterInfoColumnar.class), serialized, ParameterInfoColumnar.class);
+        testAssertEquals((ParameterInfoColumnar)parameterInfo, deserialized, "Roundtrip serialization failed");
+    }
+    @Test
+    void testParameterInfoIndexedRoundTrip() {
+        ParameterInfo parameterInfo = buildParameterInfoIndexed();
+        ContentType contentType = Formats.parseHeader(Formats.JSONV2, ParameterInfoIndexed.class);
+        String serialized = Formats.format(contentType, parameterInfo);
+        ParameterInfoIndexed deserialized = Formats.parseContent(Formats.parseHeader(Formats.JSONV2, ParameterInfoIndexed.class), serialized, ParameterInfoIndexed.class);
+        testAssertEquals((ParameterInfoIndexed) parameterInfo, deserialized, "Roundtrip serialization failed");
+    }
+
+    @Test
+    void testParameterInfoColumnarRoundTripFromFile() throws Exception {
+        ParameterInfo parameterInfo = buildParameterInfoColumnar();
+        InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/data/dto/timeseriesprofile/parameterinfocolumnar.json");
+        assertNotNull(resource);
+        String serialized = IOUtils.toString(resource, StandardCharsets.UTF_8);
+        ParameterInfoColumnar deserialized = Formats.parseContent(Formats.parseHeader(Formats.JSONV2, ParameterInfoColumnar.class), serialized, ParameterInfoColumnar.class);
+        testAssertEquals((ParameterInfoColumnar)parameterInfo, deserialized, "Roundtrip serialization from file failed");
+    }
+    @Test
+    void testParameterInfoIndexedRoundTripFromFile() throws Exception {
+        ParameterInfo parameterInfo = buildParameterInfoIndexed();
+        InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/data/dto/timeseriesprofile/parameterinfoindexed.json");
+        assertNotNull(resource);
+        String serialized = IOUtils.toString(resource, StandardCharsets.UTF_8);
+        ParameterInfoIndexed deserialized = Formats.parseContent(Formats.parseHeader(Formats.JSONV2, ParameterInfoIndexed.class), serialized, ParameterInfoIndexed.class);
+        testAssertEquals((ParameterInfoIndexed)parameterInfo, deserialized, "Roundtrip serialization from file failed");
+    }
+
+    static ParameterInfo buildParameterInfoColumnar() {
+        return new ParameterInfoColumnar.Builder()
+                .withEndColumn(20)
+                .withStartColumn(10)
+                .withParameter("Depth")
+                .withUnit("m")
+                .build();
+    }
+    static ParameterInfo buildParameterInfoIndexed() {
+        return new ParameterInfoIndexed.Builder()
+                .withIndex(1)
+                .withParameter("Depth")
+                .withUnit("m")
+                .build();
+    }
+
+    static void testAssertEquals(ParameterInfoColumnar expected, ParameterInfoColumnar actual, String message) {
+        assertEquals(expected.getParameter(), actual.getParameter(), message);
+        assertEquals(expected.getParameterInfoString(), actual.getParameterInfoString(), message);
+        assertEquals(expected.getStartColumn(), actual.getStartColumn(), message);
+        assertEquals(expected.getEndColumn(), actual.getEndColumn(), message);
+        assertEquals(expected.getUnit(), actual.getUnit(), message);
+    }
+    static void testAssertEquals(ParameterInfoIndexed expected, ParameterInfoIndexed actual, String message) {
+        assertEquals(expected.getParameter(), actual.getParameter(), message);
+        assertEquals(expected.getParameterInfoString(), actual.getParameterInfoString(), message);
+        assertEquals(expected.getIndex(), actual.getIndex(), message);
+        assertEquals(expected.getUnit(), actual.getUnit(), message);
+    }
+
+}
diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dto/timeseriesprofile/TimeSeriesProfileInstanceTest.java b/cwms-data-api/src/test/java/cwms/cda/data/dto/timeseriesprofile/TimeSeriesProfileInstanceTest.java
new file mode 100644
index 000000000..9632fe73b
--- /dev/null
+++ b/cwms-data-api/src/test/java/cwms/cda/data/dto/timeseriesprofile/TimeSeriesProfileInstanceTest.java
@@ -0,0 +1,108 @@
+package cwms.cda.data.dto.timeseriesprofile;
+
+import cwms.cda.formatters.ContentType;
+import cwms.cda.formatters.Formats;
+import org.apache.commons.io.IOUtils;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+public class TimeSeriesProfileInstanceTest {
+    private static final Map<String, String> PARAMETER_UNIT_MAP = new HashMap<>();
+    @BeforeAll
+    public static void setup() {
+        PARAMETER_UNIT_MAP.put("Depth", "ft");
+        PARAMETER_UNIT_MAP.put("Temp-Water", "F");
+    }
+    @Test
+    void testTimeSeriesProfileInstanceSerializationRoundTrip() {
+        TimeSeriesProfileInstance timeSeriesProfileInstance = buildTestTimeSeriesProfileInstance();
+        ContentType contentType = Formats.parseHeader(Formats.JSONV2, TimeSeriesProfileInstance.class);
+
+        String serialized = Formats.format(contentType, timeSeriesProfileInstance);
+        TimeSeriesProfileInstance deserialized = Formats.parseContent(Formats.parseHeader(Formats.JSONV2, TimeSeriesProfileInstance.class), serialized, TimeSeriesProfileInstance.class);
+        testAssertEquals(timeSeriesProfileInstance, deserialized, "Roundtrip serialization failed");
+    }
+    @Test
+    void testTimeSeriesProfileInstanceSerializationRoundTripFromFile() throws Exception {
+        TimeSeriesProfileInstance timeSeriesProfileInstance = buildTestTimeSeriesProfileInstance();
+        InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/data/dto/timeseriesprofile/timeseriesprofileinstance.json");
+        assertNotNull(resource);
+        String serialized = IOUtils.toString(resource, StandardCharsets.UTF_8);
+        TimeSeriesProfileInstance deserialized = Formats.parseContent(Formats.parseHeader(Formats.JSONV2, TimeSeriesProfileInstance.class), serialized, TimeSeriesProfileInstance.class);
+        testAssertEquals(timeSeriesProfileInstance, deserialized, "Roundtrip serialization from file failed");
+    }
+
+    private TimeSeriesProfileInstance buildTestTimeSeriesProfileInstance() {
+        TimeSeriesProfile timeSeriesProfile = TimeSeriesProfileTest.buildTestTimeSeriesProfile();
+        List<ProfileTimeSeries> timeSeriesList = buildTimeSeriesList(timeSeriesProfile.getParameterList());
+        return new TimeSeriesProfileInstance.Builder()
+                .withTimeSeriesProfile(timeSeriesProfile)
+                .withTimeSeriesList(timeSeriesList)
+                .withFirstDate(Instant.parse("2020-07-09T12:00:00.00Z"))
+                .withLastDate(Instant.parse("2025-07-09T12:00:00.00Z"))
+                .build();
+    }
+
+    private List<ProfileTimeSeries> buildTimeSeriesList(List<String> parameterList) {
+        List<ProfileTimeSeries> timeSeriesList = new ArrayList<>();
+        for(String parameter : parameterList)
+        {
+            ProfileTimeSeries profileTimeSeries = new ProfileTimeSeries.Builder()
+                    .withParameter(parameter)
+                    .withUnit(PARAMETER_UNIT_MAP.get(parameter))
+                    .withTimeZone("PST")
+                    .withValues(buildTimeValueList())
+                    .build();
+            timeSeriesList.add(profileTimeSeries);
+        }
+        return timeSeriesList;
+    }
+
+    private List<TimeValuePair> buildTimeValueList() {
+        List<TimeValuePair> timeValueList = new ArrayList<>();
+        timeValueList.add( new TimeValuePair.Builder().withValue(1.0).withDateTime(Instant.parse("2021-02-09T11:19:42.12Z")).build());
+        timeValueList.add( new TimeValuePair.Builder().withValue(3.0).withDateTime(Instant.parse("2021-02-09T11:19:42.22Z")).build());
+        return timeValueList;
+    }
+
+    private void testAssertEquals(TimeSeriesProfileInstance expected, TimeSeriesProfileInstance actual, String message) {
+        TimeSeriesProfileTest.testAssertEquals(expected.getTimeSeriesProfile(), actual.getTimeSeriesProfile(), message);
+        testAssertEquals(expected.getTimeSeriesList(), actual.getTimeSeriesList(), message);
+        assertEquals(expected.getFirstDate(), actual.getFirstDate());
+        assertEquals(expected.getLastDate(), actual.getLastDate());
+    }
+
+    private void testAssertEquals(List<ProfileTimeSeries> expected, List<ProfileTimeSeries> actual, String message) {
+        assertEquals(expected.size(), actual.size(), message);
+        for (int i = 0; i < expected.size(); i++) {
+            testAssertEquals(expected.get(i), actual.get(i), message);
+        }
+    }
+
+    private void testAssertEquals(ProfileTimeSeries expected, ProfileTimeSeries actual, String message) {
+        assertEquals(expected.getTimeZone(), actual.getTimeZone(), message);
+        assertEquals(expected.getParameter(), actual.getParameter(), message);
+        assertEquals(expected.getUnit(),actual.getUnit(), message);
+        testAssertEquals(expected.getValues(), actual.getValues());
+    }
+
+    private void testAssertEquals(List<TimeValuePair> expected, List<TimeValuePair> actual) {
+        assertEquals(expected.size(), actual.size());
+        for(int i=0;i<expected.size();i++)
+        {
+            assertEquals(expected.get(i).getDateTime(), actual.get(i).getDateTime());
+            assertEquals(expected.get(i).getValue(), actual.get(i).getValue());
+        }
+    }
+}
diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dto/timeseriesprofile/TimeSeriesProfileParserTest.java b/cwms-data-api/src/test/java/cwms/cda/data/dto/timeseriesprofile/TimeSeriesProfileParserTest.java
new file mode 100644
index 000000000..a052ce40a
--- /dev/null
+++ b/cwms-data-api/src/test/java/cwms/cda/data/dto/timeseriesprofile/TimeSeriesProfileParserTest.java
@@ -0,0 +1,165 @@
+package cwms.cda.data.dto.timeseriesprofile;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+import cwms.cda.data.dto.CwmsId;
+import cwms.cda.formatters.ContentType;
+import cwms.cda.formatters.Formats;
+import org.apache.commons.io.IOUtils;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+final class TimeSeriesProfileParserTest {
+    @Test
+    void testTimeSeriesProfileColumnarSerializationRoundTrip() {
+        TimeSeriesProfileParserColumnar timeSeriesProfileParser = buildTestTimeSeriesProfileParserColumnar();
+        ContentType contentType = Formats.parseHeader(Formats.JSONV2, TimeSeriesProfileParserColumnar.class);
+
+        String serialized = Formats.format(contentType, timeSeriesProfileParser);
+        TimeSeriesProfileParserColumnar deserialized = Formats.parseContent(Formats.parseHeader(Formats.JSONV2, TimeSeriesProfileParserColumnar.class), serialized, TimeSeriesProfileParserColumnar.class);
+        testAssertEquals(timeSeriesProfileParser,  deserialized, "Roundtrip serialization failed");
+    }
+
+    @Test
+    void testTimeSeriesProfileIndexedSerializationRoundTrip() {
+        TimeSeriesProfileParser timeSeriesProfileParser = buildTestTimeSeriesProfileParserIndexed();
+        ContentType contentType = Formats.parseHeader(Formats.JSONV2, TimeSeriesProfileParserColumnar.class);
+
+        String serialized = Formats.format(contentType, timeSeriesProfileParser);
+        TimeSeriesProfileParserIndexed deserialized = Formats.parseContent(Formats.parseHeader(Formats.JSONV2, TimeSeriesProfileParserIndexed.class), serialized, TimeSeriesProfileParserIndexed.class);
+        testAssertEquals((TimeSeriesProfileParserIndexed)timeSeriesProfileParser, deserialized, "Roundtrip serialization failed");
+    }
+    @Test
+    void testTimeSeriesProfileSerializationRoundTripColumnarFromFile() throws Exception {
+        TimeSeriesProfileParserColumnar timeSeriesProfileParser = buildTestTimeSeriesProfileParserColumnar();
+        InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/data/dto/timeseriesprofile/timeseriesprofileparsercolumnar.json");
+        assertNotNull(resource);
+        String serialized = IOUtils.toString(resource, StandardCharsets.UTF_8);
+        TimeSeriesProfileParserColumnar deserialized = Formats.parseContent(Formats.parseHeader(Formats.JSONV2, TimeSeriesProfileParserColumnar.class), serialized, TimeSeriesProfileParserColumnar.class);
+        testAssertEquals(timeSeriesProfileParser,  deserialized, "Roundtrip serialization from file failed");
+    }
+
+    @Test
+    void testTimeSeriesProfileSerializationRoundTripIndexedFromFile() throws Exception {
+        TimeSeriesProfileParser timeSeriesProfileParser = buildTestTimeSeriesProfileParserIndexed();
+        InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/data/dto/timeseriesprofile/timeseriesprofileparserindexed.json");
+        assertNotNull(resource);
+        String serialized = IOUtils.toString(resource, StandardCharsets.UTF_8);
+        TimeSeriesProfileParserIndexed deserialized = Formats.parseContent(Formats.parseHeader(Formats.JSONV2, TimeSeriesProfileParserIndexed.class), serialized, TimeSeriesProfileParserIndexed.class);
+        testAssertEquals((TimeSeriesProfileParserIndexed) timeSeriesProfileParser, deserialized, "Roundtrip serialization from file failed");
+    }
+
+    private static TimeSeriesProfileParserColumnar buildTestTimeSeriesProfileParserColumnar() {
+        List<ParameterInfo> parameterInfo = new ArrayList<>();
+        parameterInfo.add(new ParameterInfoColumnar.Builder()
+                .withStartColumn(11)
+                .withEndColumn(20)
+                .withParameter("Depth")
+                .withUnit("m")
+               .build());
+        parameterInfo.add(new ParameterInfoColumnar.Builder()
+                .withStartColumn(21)
+                .withEndColumn(30)
+                .withParameter("Temp-Water")
+                .withUnit("F")
+                 .build());
+        CwmsId locationId = new CwmsId.Builder()
+                .withOfficeId("SWT")
+                .withName("location")
+                .build();
+        return (TimeSeriesProfileParserColumnar)
+                new TimeSeriesProfileParserColumnar.Builder()
+                        .withTimeStartColumn(1)
+                        .withTimeEndColumn(10)
+                        .withLocationId(locationId)
+                        .withKeyParameter("Depth")
+                        .withRecordDelimiter((char) 10)
+                        .withTimeFormat("MM/DD/YYYY,HH24:MI:SS")
+                        .withTimeZone("UTC")
+                        .withTimeInTwoFields(false)
+                        .withParameterInfoList(parameterInfo)
+                        .build();
+    }
+    private static TimeSeriesProfileParser buildTestTimeSeriesProfileParserIndexed() {
+        List<ParameterInfo> parameterInfo = new ArrayList<>();
+        parameterInfo.add(new ParameterInfoIndexed.Builder()
+                .withIndex(3)
+                .withParameter("Depth")
+                .withUnit("m")
+                .build());
+        parameterInfo.add(new ParameterInfoIndexed.Builder()
+                .withIndex(5)
+                .withParameter("Temp-Water")
+                .withUnit("F")
+                .build());
+        CwmsId locationId = new CwmsId.Builder()
+                .withOfficeId("SWT")
+                .withName("location")
+                .build();
+        return
+                new TimeSeriesProfileParserIndexed.Builder()
+                        .withTimeField(1)
+                        .withFieldDelimiter(',')
+                        .withLocationId(locationId)
+                        .withKeyParameter("Depth")
+                        .withRecordDelimiter((char) 10)
+                        .withTimeFormat("MM/DD/YYYY,HH24:MI:SS")
+                        .withTimeZone("UTC")
+                        .withTimeInTwoFields(false)
+                        .withParameterInfoList(parameterInfo)
+                        .build();
+    }
+
+    private void testAssertEquals(TimeSeriesProfileParserColumnar expected, TimeSeriesProfileParserColumnar actual, String message) {
+        assertEquals(expected.getLocationId().getName(), actual.getLocationId().getName(), message);
+        assertEquals(expected.getLocationId().getOfficeId(), actual.getLocationId().getOfficeId(), message);
+        assertEquals(expected.getLocationId().getName(), actual.getLocationId().getName(), message);
+        assertEquals(expected.getLocationId().getOfficeId(), actual.getLocationId().getOfficeId(), message);
+        assertEquals(expected.getKeyParameter(), actual.getKeyParameter(), message);
+        assertEquals(expected.getTimeFormat(), actual.getTimeFormat(), message);
+        testAssertEquals(expected.getParameterInfoList(), actual.getParameterInfoList(),message);
+        assertEquals(expected.getRecordDelimiter(), actual.getRecordDelimiter(), message);
+        assertEquals(expected.getTimeZone(), actual.getTimeZone());
+        assertEquals(expected.getTimeInTwoFields(), actual.getTimeInTwoFields());
+        assertEquals(expected.getTimeEndColumn(), actual.getTimeEndColumn());
+        assertEquals(expected.getTimeStartColumn(), actual.getTimeStartColumn());
+    }
+    private void testAssertEquals(TimeSeriesProfileParserIndexed expected, TimeSeriesProfileParserIndexed actual, String message) {
+        assertEquals(expected.getLocationId().getName(), actual.getLocationId().getName(), message);
+        assertEquals(expected.getLocationId().getOfficeId(), actual.getLocationId().getOfficeId(), message);
+        assertEquals(expected.getLocationId().getName(), actual.getLocationId().getName(), message);
+        assertEquals(expected.getLocationId().getOfficeId(), actual.getLocationId().getOfficeId(), message);
+        assertEquals(expected.getKeyParameter(), actual.getKeyParameter(), message);
+        assertEquals(expected.getTimeFormat(), actual.getTimeFormat(), message);
+        testAssertEquals(expected.getParameterInfoList(), actual.getParameterInfoList(),message);
+        assertEquals(expected.getRecordDelimiter(), actual.getRecordDelimiter(), message);
+        assertEquals(expected.getTimeZone(), actual.getTimeZone());
+        assertEquals(expected.getTimeInTwoFields(), actual.getTimeInTwoFields());
+        assertEquals(expected.getTimeField(), actual.getTimeField());
+        assertEquals(expected.getFieldDelimiter(), actual.getFieldDelimiter());
+    }
+    private void testAssertEquals(List<ParameterInfo> expected, List<ParameterInfo> actual, String message) {
+        assertEquals(expected.size(), actual.size());
+        for(int i=0;i<expected.size();i++)
+        {
+            assertEquals(expected.get(i).getParameter(), actual.get(i).getParameter(), message);
+            assertEquals(expected.get(i).getUnit(), actual.get(i).getUnit(), message);
+            if(expected.get(i) instanceof ParameterInfoIndexed &&  actual.get(i) instanceof ParameterInfoIndexed) {
+                assertEquals(((ParameterInfoIndexed) expected.get(i)).getIndex(), ((ParameterInfoIndexed) actual.get(i)).getIndex(), message);
+            }
+            else if(expected.get(i) instanceof ParameterInfoColumnar &&  actual.get(i) instanceof ParameterInfoColumnar) {
+                assertEquals(((ParameterInfoColumnar) expected.get(i)).getStartColumn(), ((ParameterInfoColumnar) actual.get(i)).getStartColumn(), message);
+                assertEquals(((ParameterInfoColumnar) expected.get(i)).getEndColumn(), ((ParameterInfoColumnar) actual.get(i)).getEndColumn(), message);
+            }
+            else
+            {
+                assertEquals(expected.get(i).getClass(), actual.get(i).getClass());
+            }
+        }
+    }
+}
diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dto/timeseriesprofile/TimeSeriesProfileTest.java b/cwms-data-api/src/test/java/cwms/cda/data/dto/timeseriesprofile/TimeSeriesProfileTest.java
new file mode 100644
index 000000000..8a904779c
--- /dev/null
+++ b/cwms-data-api/src/test/java/cwms/cda/data/dto/timeseriesprofile/TimeSeriesProfileTest.java
@@ -0,0 +1,69 @@
+package cwms.cda.data.dto.timeseriesprofile;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collections;
+
+import cwms.cda.data.dto.CwmsId;
+import cwms.cda.formatters.ContentType;
+import cwms.cda.formatters.Formats;
+import org.apache.commons.io.IOUtils;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+final public class TimeSeriesProfileTest {
+    @Test
+    void testTimeSeriesProfileSerializationRoundTrip() {
+        TimeSeriesProfile timeSeriesProfile = buildTestTimeSeriesProfile();
+        ContentType contentType = Formats.parseHeader(Formats.JSONV2, TimeSeriesProfile.class);
+        String serialized = Formats.format(contentType, timeSeriesProfile);
+        TimeSeriesProfile deserialized = Formats.parseContent(Formats.parseHeader(Formats.JSONV2, TimeSeriesProfile.class), serialized, TimeSeriesProfile.class);
+        testAssertEquals(timeSeriesProfile, deserialized, "Roundtrip serialization failed");
+    }
+
+    @Test
+    void testTimeSeriesProfileSerializationRoundTripFromFile() throws Exception {
+        TimeSeriesProfile timeSeriesProfile = buildTestTimeSeriesProfile();
+        InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/data/dto/timeseriesprofile/timeseriesprofile.json");
+        assertNotNull(resource);
+        String serialized = IOUtils.toString(resource, StandardCharsets.UTF_8);
+        TimeSeriesProfile deserialized = Formats.parseContent(Formats.parseHeader(Formats.JSONV2, TimeSeriesProfile.class), serialized, TimeSeriesProfile.class);
+        testAssertEquals(timeSeriesProfile, deserialized, "Roundtrip serialization from file failed");
+    }
+
+     static TimeSeriesProfile buildTestTimeSeriesProfile() {
+        CwmsId locationId = new CwmsId.Builder()
+                .withName("SWAN")
+                .withOfficeId("SWT")
+                .build();
+        CwmsId referenceTsId = new CwmsId.Builder()
+                .withName("SWAN.Elev.Inst.1Hour.0.DSS-Obs")
+                .withOfficeId("SWT")
+                .build();
+        return new TimeSeriesProfile.Builder()
+                .withKeyParameter("Depth")
+                .withReferenceTsId(referenceTsId)
+                .withLocationId(locationId)
+                .withDescription("Description")
+                .withParameterList(Arrays.asList("Temp-Water", "Depth"))
+                .build();
+    }
+
+    public static void testAssertEquals(TimeSeriesProfile expected, TimeSeriesProfile actual, String message) {
+        assertEquals(expected.getLocationId().getName(), actual.getLocationId().getName(), message);
+        assertEquals(expected.getLocationId().getOfficeId(), actual.getLocationId().getOfficeId(), message);
+        assertEquals(expected.getDescription(), actual.getDescription(), message);
+        assertEquals(expected.getParameterList(), actual.getParameterList(), message);
+        assertEquals(expected.getKeyParameter(), actual.getKeyParameter(), message);
+        if(expected.getReferenceTsId()!=null &&  actual.getReferenceTsId()!=null) {
+            assertEquals(expected.getReferenceTsId().getName(), actual.getReferenceTsId().getName(), message);
+            assertEquals(expected.getReferenceTsId().getOfficeId(), actual.getReferenceTsId().getOfficeId(), message);
+        }
+        else {
+            assertEquals(expected.getReferenceTsId(), actual.getReferenceTsId(),message);
+        }
+    }
+}
diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dto/timeseriesprofile/TimeValuePairTest.java b/cwms-data-api/src/test/java/cwms/cda/data/dto/timeseriesprofile/TimeValuePairTest.java
new file mode 100644
index 000000000..174f9329b
--- /dev/null
+++ b/cwms-data-api/src/test/java/cwms/cda/data/dto/timeseriesprofile/TimeValuePairTest.java
@@ -0,0 +1,48 @@
+package cwms.cda.data.dto.timeseriesprofile;
+
+import cwms.cda.formatters.ContentType;
+import cwms.cda.formatters.Formats;
+import org.apache.commons.io.IOUtils;
+import org.junit.jupiter.api.Test;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+public class TimeValuePairTest {
+    @Test
+    void testTimeValuePairRoundTrip() {
+        TimeValuePair timeValuePair = buildTestTimeValuePair();
+        ContentType contentType = Formats.parseHeader(Formats.JSONV2, TimeValuePair.class);
+        String serialized = Formats.format(contentType, timeValuePair);
+        TimeValuePair deserialized = Formats.parseContent(Formats.parseHeader(Formats.JSONV2, TimeValuePair.class), serialized, TimeValuePair.class);
+        testAssertEquals(timeValuePair, deserialized, "Roundtrip serialization failed");
+    }
+
+    @Test
+    void testTimeValuePairSerializationRoundTripFromFile() throws Exception {
+        TimeValuePair timeValuePair = buildTestTimeValuePair();
+        InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/data/dto/timeseriesprofile/timevaluepair.json");
+        assertNotNull(resource);
+        String serialized = IOUtils.toString(resource, StandardCharsets.UTF_8);
+        TimeValuePair deserialized = Formats.parseContent(Formats.parseHeader(Formats.JSONV2, TimeValuePair.class), serialized, TimeValuePair.class);
+        testAssertEquals(timeValuePair, deserialized, "Roundtrip serialization from file failed");
+    }
+
+    static TimeValuePair buildTestTimeValuePair() {
+        return new TimeValuePair.Builder()
+                .withValue(1.0)
+                .withQuality(0)
+                .withDateTime(Instant.parse("2025-07-09T12:00:00.00Z"))
+                .build();
+    }
+
+    public static void testAssertEquals(TimeValuePair expected, TimeValuePair actual, String message) {
+        assertEquals(expected.getValue(), actual.getValue(), message);
+        assertEquals(expected.getDateTime(), actual.getDateTime(), message);
+        assertEquals(expected.getQuality(), actual.getQuality(), message);
+    }
+
+}
diff --git a/cwms-data-api/src/test/resources/cwms/cda/data/dto/timeseriesprofile/parameterinfocolumnar.json b/cwms-data-api/src/test/resources/cwms/cda/data/dto/timeseriesprofile/parameterinfocolumnar.json
new file mode 100644
index 000000000..cf686f8de
--- /dev/null
+++ b/cwms-data-api/src/test/resources/cwms/cda/data/dto/timeseriesprofile/parameterinfocolumnar.json
@@ -0,0 +1,7 @@
+{
+  "type": "columnar-parameter-info",
+  "parameter": "Depth",
+  "unit": "m",
+  "start-column": 10,
+  "end-column": 20
+}
\ No newline at end of file
diff --git a/cwms-data-api/src/test/resources/cwms/cda/data/dto/timeseriesprofile/parameterinfoindexed.json b/cwms-data-api/src/test/resources/cwms/cda/data/dto/timeseriesprofile/parameterinfoindexed.json
new file mode 100644
index 000000000..aab556f95
--- /dev/null
+++ b/cwms-data-api/src/test/resources/cwms/cda/data/dto/timeseriesprofile/parameterinfoindexed.json
@@ -0,0 +1,6 @@
+{
+  "type": "indexed-parameter-info",
+  "parameter": "Depth",
+  "unit": "m",
+  "index": 1
+}
\ No newline at end of file
diff --git a/cwms-data-api/src/test/resources/cwms/cda/data/dto/timeseriesprofile/timeSeriesProfileData.txt b/cwms-data-api/src/test/resources/cwms/cda/data/dto/timeseriesprofile/timeSeriesProfileData.txt
new file mode 100644
index 000000000..e41d591eb
--- /dev/null
+++ b/cwms-data-api/src/test/resources/cwms/cda/data/dto/timeseriesprofile/timeSeriesProfileData.txt
@@ -0,0 +1,28 @@
+'sep=, Date,Time,Site,Unit ID,User ID,F,mmHg,DO %,DO mg/L,SPC-uS/cm,pH,NTU,
+BGA-PCRFU-18H110486,BGA-PCug/L-18H110486,Chl RFU-18H110486,Chl ug/L-18H110486,DEP ft-18H105572,Batt V-18H111574,Lat-18H111574,Lon-18H111574,
+09/09/2019,12:48:57,DET,,Holly Bellringer,67.108,719.6,98.3,9.03,43.9,8.54,0.19,0.21,-0.65,0.17,-0.01,3.152,5.35,44.71898,-122.24620,
+09/09/2019,12:49:19,DET,,Holly Bellringer,67.563,719.6,97.2,8.88,43.4,8.43,0.12,0.21,-0.65,0.19,0.05,4.028,5.34,44.71898,-122.24620,
+09/09/2019,12:52:54,DET,,Holly Bellringer,67.820,719.5,96.8,8.82,43.3,8.04,0.23,0.14,-0.71,0.20,0.10,6.264,5.32,44.71899,-122.24610,
+09/09/2019,12:53:17,DET,,Holly Bellringer,67.767,719.6,96.6,8.81,43.3,8.02,0.12,0.15,-0.70,0.24,0.24,8.525,5.31,44.71900,-122.24610,
+09/09/2019,12:54:42,DET,,Holly Bellringer,67.718,719.6,96.5,8.80,43.3,7.97,0.11,0.13,-0.72,0.25,0.29,10.789,5.31,44.71898,-122.24620,
+09/09/2019,12:55:50,DET,,Holly Bellringer,67.700,719.6,96.4,8.79,43.3,7.95,0.08,0.17,-0.68,0.23,0.19,12.045,5.27,44.71898,-122.24620,
+09/09/2019,12:56:27,DET,,Holly Bellringer,67.682,719.5,96.3,8.79,43.2,7.92,0.08,0.16,-0.70,0.24,0.24,14.188,5.28,44.71897,-122.24620,
+09/09/2019,12:56:54,DET,,Holly Bellringer,67.669,719.5,96.2,8.78,43.2,7.92,0.17,0.10,-0.75,0.27,0.39,16.218,5.26,44.71897,-122.24620,
+09/09/2019,12:57:48,DET,,Holly Bellringer,67.651,719.5,96.1,8.77,43.2,7.86,0.13,0.10,-0.75,0.22,0.16,18.303,5.26,44.71901,-122.24620,
+09/09/2019,12:58:57,DET,,Holly Bellringer,67.613,719.6,95.8,8.75,43.2,7.86,0.05,0.15,-0.70,0.24,0.26,23.081,5.24,44.71901,-122.24620,
+09/09/2019,12:59:31,DET,,Holly Bellringer,67.572,719.6,95.3,8.71,43.2,7.83,0.12,0.12,-0.74,0.30,0.48,28.262,5.26,44.71900,-122.24620,
+09/09/2019,13:00:37,DET,,Holly Bellringer,67.557,719.6,95.1,8.69,43.2,7.82,0.07,0.11,-0.74,0.27,0.39,33.043,5.28,44.71904,-122.24610,
+09/09/2019,13:01:08,DET,,Holly Bellringer,67.495,719.6,94.6,8.65,43.2,7.78,0.17,0.15,-0.71,0.26,0.35,38.527,5.27,44.71902,-122.24620,
+09/09/2019,13:02:17,DET,,Holly Bellringer,67.003,719.7,91.4,8.40,43.4,7.74,0.14,0.26,-0.59,0.57,1.55,43.937,5.28,44.71902,-122.24620,
+09/09/2019,13:02:51,DET,,Holly Bellringer,64.150,719.6,77.9,7.40,44.2,7.63,0.12,0.15,-0.70,0.33,0.62,48.191,5.27,44.71902,-122.24620,
+09/09/2019,13:03:50,DET,,Holly Bellringer,62.782,719.6,71.1,6.86,44.2,7.49,0.13,0.26,-0.59,0.40,0.88,53.288,5.28,44.71902,-122.24620,
+09/09/2019,13:04:50,DET,,Holly Bellringer,61.698,719.7,65.8,6.43,43.5,7.37,0.19,0.22,-0.63,0.37,0.76,58.457,5.28,44.71902,-122.24620,
+09/09/2019,13:07:48,DET,,Holly Bellringer,54.818,719.8,61.8,6.56,39.4,7.12,0.67,0.18,-0.67,0.12,-0.23,88.792,5.29,44.71902,-122.24620,
+09/09/2019,13:08:57,DET,,Holly Bellringer,52.003,719.8,68.4,7.53,37.9,7.07,0.08,0.28,-0.58,0.13,-0.19,103.598,5.28,44.71904,-122.24620,
+09/09/2019,13:09:46,DET,,Holly Bellringer,49.363,719.8,73.9,8.42,37.4,7.05,0.17,0.23,-0.63,0.13,-0.19,118.692,5.30,44.71901,-122.24620,
+09/09/2019,13:11:20,DET,,Holly Bellringer,47.156,719.8,76.6,8.98,37.2,7.03,0.28,0.28,-0.58,0.10,-0.29,133.659,5.32,44.71902,-122.24620,
+09/09/2019,13:12:45,DET,,Holly Bellringer,45.468,719.8,75.9,9.11,37.0,7.00,0.43,0.30,-0.55,0.11,-0.26,148.541,5.31,44.71902,-122.24620,
+09/09/2019,13:13:33,DET,,Holly Bellringer,44.743,719.8,75.4,9.13,37.0,6.99,0.52,0.35,-0.50,0.10,-0.30,163.665,5.31,44.71900,-122.24620,
+09/09/2019,13:14:49,DET,,Holly Bellringer,44.135,719.8,75.5,9.22,36.9,6.98,0.67,0.30,-0.55,0.13,-0.20,178.560,5.31,44.71901,-122.24620,
+09/09/2019,13:16:01,DET,,Holly Bellringer,43.701,719.8,75.7,9.30,36.9,6.97,0.88,0.31,-0.54,0.08,-0.38,193.010,5.32,44.71903,-122.24620,
+09/09/2019,13:17:20,DET,,Holly Bellringer,43.390,719.8,75.6,9.33,36.9,6.96,1.00,0.32,-0.53,0.10,-0.31,208.607,5.31,44.71902,-122.24620,'
diff --git a/cwms-data-api/src/test/resources/cwms/cda/data/dto/timeseriesprofile/timeSeriesProfileDataColumnar.txt b/cwms-data-api/src/test/resources/cwms/cda/data/dto/timeseriesprofile/timeSeriesProfileDataColumnar.txt
new file mode 100644
index 000000000..a416931cb
--- /dev/null
+++ b/cwms-data-api/src/test/resources/cwms/cda/data/dto/timeseriesprofile/timeSeriesProfileDataColumnar.txt
@@ -0,0 +1,4 @@
+header
+09/09/2019,12:49:07,111,222
+09/09/2019,12:49:19,111,222
+09/09/2019,13:17:20,111,222
\ No newline at end of file
diff --git a/cwms-data-api/src/test/resources/cwms/cda/data/dto/timeseriesprofile/timeseriesprofile.json b/cwms-data-api/src/test/resources/cwms/cda/data/dto/timeseriesprofile/timeseriesprofile.json
new file mode 100644
index 000000000..e9925caa5
--- /dev/null
+++ b/cwms-data-api/src/test/resources/cwms/cda/data/dto/timeseriesprofile/timeseriesprofile.json
@@ -0,0 +1,16 @@
+{
+  "description": "Description",
+  "parameter-list": [
+    "Temp-Water",
+    "Depth"
+  ],
+  "location-id": {
+    "office-id": "SWT",
+    "name": "SWAN"
+  },
+  "reference-ts-id": {
+    "office-id": "SWT",
+    "name": "SWAN.Elev.Inst.1Hour.0.DSS-Obs"
+  },
+  "key-parameter": "Depth"
+}
diff --git a/cwms-data-api/src/test/resources/cwms/cda/data/dto/timeseriesprofile/timeseriesprofileinstance.json b/cwms-data-api/src/test/resources/cwms/cda/data/dto/timeseriesprofile/timeseriesprofileinstance.json
new file mode 100644
index 000000000..4583e0432
--- /dev/null
+++ b/cwms-data-api/src/test/resources/cwms/cda/data/dto/timeseriesprofile/timeseriesprofileinstance.json
@@ -0,0 +1,56 @@
+{
+  "time-series-profile": {
+    "location-id": {
+      "office-id": "SWT",
+      "name": "SWAN"
+    },
+    "description": "Description",
+    "parameter-list": [
+      "Temp-Water",
+      "Depth"
+    ],
+    "key-parameter": "Depth",
+    "reference-ts-id": {
+      "office-id": "SWT",
+      "name": "SWAN.Elev.Inst.1Hour.0.DSS-Obs"
+    }
+  },
+  "time-series-list": [
+    {
+      "parameter": "Temp-Water",
+      "unit": "F",
+      "time-zone": "PST",
+      "values": [
+        {
+          "date-time": 1612869582120,
+          "value": 1.0,
+          "quality": 0
+        },
+        {
+          "date-time": 1612869582220,
+          "value": 3.0,
+          "quality": 0
+        }
+      ]
+    },
+    {
+      "parameter": "Depth",
+      "unit": "ft",
+      "time-zone": "PST",
+      "values": [
+        {
+          "date-time": 1612869582120,
+          "value": 1.0,
+          "quality": 0
+        },
+        {
+          "date-time": 1612869582220,
+          "value": 3.0,
+          "quality": 0
+        }
+      ]
+    }
+  ],
+  "first-date": 1594296000000,
+  "last-date": 1752062400000
+}
\ No newline at end of file
diff --git a/cwms-data-api/src/test/resources/cwms/cda/data/dto/timeseriesprofile/timeseriesprofileparsercolumnar.json b/cwms-data-api/src/test/resources/cwms/cda/data/dto/timeseriesprofile/timeseriesprofileparsercolumnar.json
new file mode 100644
index 000000000..727e9f4cc
--- /dev/null
+++ b/cwms-data-api/src/test/resources/cwms/cda/data/dto/timeseriesprofile/timeseriesprofileparsercolumnar.json
@@ -0,0 +1,30 @@
+{
+  "type": "columnar-timeseries-profile-parser",
+  "location-id": {
+    "office-id": "SWT",
+    "name": "location"
+  },
+  "key-parameter": "Depth",
+  "record-delimiter": "\n",
+  "time-format": "MM/DD/YYYY,HH24:MI:SS",
+  "time-zone": "UTC",
+  "parameter-info-list": [
+    {
+      "type": "columnar-parameter-info",
+      "parameter": "Depth",
+      "unit": "m",
+      "start-column": 11,
+      "end-column": 20
+    },
+    {
+      "type": "columnar-parameter-info",
+      "parameter": "Temp-Water",
+      "unit": "F",
+      "start-column": 21,
+      "end-column": 30
+    }
+  ],
+  "time-in-two-fields": false,
+  "time-start-column": 1,
+  "time-end-column": 10
+}
\ No newline at end of file
diff --git a/cwms-data-api/src/test/resources/cwms/cda/data/dto/timeseriesprofile/timeseriesprofileparserindexed.json b/cwms-data-api/src/test/resources/cwms/cda/data/dto/timeseriesprofile/timeseriesprofileparserindexed.json
new file mode 100644
index 000000000..864f73123
--- /dev/null
+++ b/cwms-data-api/src/test/resources/cwms/cda/data/dto/timeseriesprofile/timeseriesprofileparserindexed.json
@@ -0,0 +1,28 @@
+{
+  "type": "indexed-timeseries-profile-parser",
+  "location-id": {
+    "office-id": "SWT",
+    "name": "location"
+  },
+  "key-parameter": "Depth",
+  "record-delimiter": "\n",
+  "time-format": "MM/DD/YYYY,HH24:MI:SS",
+  "time-zone": "UTC",
+  "parameter-info-list": [
+    {
+      "type": "indexed-parameter-info",
+      "parameter": "Depth",
+      "unit": "m",
+      "index": 3
+    },
+    {
+      "type": "indexed-parameter-info",
+      "parameter": "Temp-Water",
+      "unit": "F",
+      "index": 5
+    }
+  ],
+  "time-in-two-fields": false,
+  "field-delimiter": ",",
+  "time-field": 1
+}
\ No newline at end of file
diff --git a/cwms-data-api/src/test/resources/cwms/cda/data/dto/timeseriesprofile/timevaluepair.json b/cwms-data-api/src/test/resources/cwms/cda/data/dto/timeseriesprofile/timevaluepair.json
new file mode 100644
index 000000000..eee23352e
--- /dev/null
+++ b/cwms-data-api/src/test/resources/cwms/cda/data/dto/timeseriesprofile/timevaluepair.json
@@ -0,0 +1,5 @@
+{
+  "date-time": 1752062400000,
+  "value": 1.0,
+  "quality": 0
+}
\ No newline at end of file