Repository: google/exposure-notifications-server
Pull request #532 (2020-06-03 16:23:24)

refactor API definitions to PKG instead of internal so they can be in…
…cluded via our go mod.

Setup for #530
And not quite what is asked for in #495  - but makes the structs for the API importable via the pkg/api package.

google-oss-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/google/exposure-notifications-server/pull/532#" title="Author self-approved">mikehelmick</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=google%2Fexposure-notifications-server).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/google/exposure-notifications-server/blob/master/OWNERS)~~ [mikehelmick]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
mikehelmick: /retest

Hoping we don't have a flake.
google-oss-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/google/exposure-notifications-server/pull/532#" title="Author self-approved">mikehelmick</a>*, *<a href="https://github.com/google/exposure-notifications-server/pull/532#pullrequestreview-424003233" title="Approved">sethvargo</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=google%2Fexposure-notifications-server).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/google/exposure-notifications-server/blob/master/OWNERS)~~ [mikehelmick,sethvargo]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
google-oss-robot: New changes are detected. LGTM label has been removed.
mikehelmick: resolved merge conflict.
sethvargo: /lgtm

Patch:
diff --git a/internal/export/worker.go b/internal/export/worker.go
index ada4201..22663fa 100644
--- a/internal/export/worker.go
+++ b/internal/export/worker.go
@@ -34,6 +34,8 @@ import (
 
 	"github.com/google/exposure-notifications-server/internal/logging"
 	"github.com/google/exposure-notifications-server/internal/util"
+
+	verifyapi "github.com/google/exposure-notifications-server/pkg/api/v1alpha1"
 )
 
 const (
@@ -317,7 +319,7 @@ func ensureMinNumExposures(exposures []*publishmodel.Exposure, region string, mi
 		// Pieces needed are
 		// (1) exposure key, (2) interval number, (3) transmission risk
 		// Exposure key is 16 random bytes.
-		eKey := make([]byte, publishmodel.KeyLength)
+		eKey := make([]byte, verifyapi.KeyLength)
 		_, err := rand.Read(eKey)
 		if err != nil {
 			return nil, fmt.Errorf("rand.Read: %w", err)
diff --git a/internal/federationin/federationin.go b/internal/federationin/federationin.go
index 276a1ae..ad6b798 100644
--- a/internal/federationin/federationin.go
+++ b/internal/federationin/federationin.go
@@ -36,6 +36,7 @@ import (
 	publishdb "github.com/google/exposure-notifications-server/internal/publish/database"
 	publishmodel "github.com/google/exposure-notifications-server/internal/publish/model"
 	"github.com/google/exposure-notifications-server/internal/serverenv"
+	verifyapi "github.com/google/exposure-notifications-server/pkg/api/v1alpha1"
 
 	"google.golang.org/api/idtoken"
 	"google.golang.org/grpc"
@@ -263,7 +264,7 @@ func pull(ctx context.Context, metrics metrics.Exporter, deps pullDependencies,
 
 			for _, cti := range ctr.ContactTracingInfo {
 				for _, key := range cti.ExposureKeys {
-					if cti.TransmissionRisk < publishmodel.MinTransmissionRisk || cti.TransmissionRisk > publishmodel.MaxTransmissionRisk {
+					if cti.TransmissionRisk < verifyapi.MinTransmissionRisk || cti.TransmissionRisk > verifyapi.MaxTransmissionRisk {
 						logger.Errorf("invalid transmission risk %v - dropping record.", cti.TransmissionRisk)
 						continue
 					}
diff --git a/internal/generate/handler.go b/internal/generate/handler.go
index 978c25a..210afe5 100644
--- a/internal/generate/handler.go
+++ b/internal/generate/handler.go
@@ -28,6 +28,7 @@ import (
 	"github.com/google/exposure-notifications-server/internal/publish/model"
 	"github.com/google/exposure-notifications-server/internal/serverenv"
 	"github.com/google/exposure-notifications-server/internal/util"
+	verifyapi "github.com/google/exposure-notifications-server/pkg/api/v1alpha1"
 )
 
 func NewHandler(ctx context.Context, config *Config, env *serverenv.ServerEnv) (http.Handler, error) {
@@ -80,7 +81,7 @@ func (h *generateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		if err != nil {
 			tr = 0
 		}
-		publish := model.Publish{
+		publish := verifyapi.Publish{
 			Keys:           util.GenerateExposureKeys(h.config.KeysPerExposure, tr, false),
 			Regions:        regions,
 			AppPackageName: "generated.data",
diff --git a/internal/publish/model/exposure_model.go b/internal/publish/model/exposure_model.go
index e4cab04..ea87013 100644
--- a/internal/publish/model/exposure_model.go
+++ b/internal/publish/model/exposure_model.go
@@ -25,63 +25,11 @@ import (
 	verifyapi "github.com/google/exposure-notifications-server/pkg/api/v1alpha1"
 )
 
-const (
-	// 21 Days worth of keys is the maximum per publish request (inclusive)
-	maxKeysPerPublish = 21
-
-	// only valid exposure key keyLength
-	KeyLength = 16
-
-	// Transmission risk constraints (inclusive..inclusive)
-	MinTransmissionRisk = 0 // 0 indicates, no/unknown risk.
-	MaxTransmissionRisk = 8
-
-	// Intervals are defined as 10 minute periods, there are 144 of them in a day.
-	// IntervalCount constraints (inclusive..inclusive)
-	MinIntervalCount = 1
-	MaxIntervalCount = 144
-
-	// Self explanatory.
-	// oneDay = time.Hour * 24
-
-	// interval length
-	intervalLength = 10 * time.Minute
-)
-
-// Publish represents the body of the PublishInfectedIds API call.
-// Keys: Required and must have length >= 1 and <= 21 (`maxKeysPerPublish`)
-// Regions: Array of regions. System defined, must match configuration.
-// AppPackageName: The identifier for the mobile application.
-//  - Android: The App Package AppPackageName
-//  - iOS: The BundleID
-// TransmissionRisk: An integer from 0-8 (inclusive) that represents
-//  the transmission risk for this publish.
-// Verification: The attestation payload for this request. (iOS or Android specific)
-//   Base64 encoded.
-// VerificationAuthorityName: a string that should be verified against the code provider.
-//  Note: This project doesn't directly include a diagnosis code verification System
-//        but does provide the ability to configure one in `serverevn.ServerEnv`
-//
-// The following fields are deprecated, but accepted for backwards-compatibility:
-// DeviceVerificationPayload: (attestation)
-// Platform: "ios" or "android"
-type Publish struct {
-	Keys                []ExposureKey `json:"temporaryExposureKeys"`
-	Regions             []string      `json:"regions"`
-	AppPackageName      string        `json:"appPackageName"`
-	VerificationPayload string        `json:"verificationPayload"`
-	HMACKey             string        `json:"hmackey"`
-	Padding             string        `json:"padding"`
-
-	Platform                  string `json:"platform"`                  // DEPRECATED
-	DeviceVerificationPayload string `json:"deviceVerificationPayload"` // DEPRECATED
-}
-
 // ApplyTransmissionRiskOverrides modifies the transmission risk values in the publish request
 // based on the provided TransmissionRiskVector.
 // In the live system, the TransmissionRiskVector values come from a trusted public health authority
 // and are embedded in the verification certificate (JWT) transmitted on the publish request.
-func (p *Publish) ApplyTransmissionRiskOverrides(overrides verifyapi.TransmissionRiskVector) {
+func ApplyTransmissionRiskOverrides(p *verifyapi.Publish, overrides verifyapi.TransmissionRiskVector) {
 	if len(overrides) == 0 {
 		return
 	}
@@ -115,33 +63,6 @@ func (p *Publish) ApplyTransmissionRiskOverrides(overrides verifyapi.Transmissio
 	}
 }
 
-// ExposureKey is the 16 byte key, the start time of the key and the
-// duration of the key. A duration of 0 means 24 hours.
-// - ALL fields are REQUIRED and must meet the constraints below.
-// Key must be the base64 (RFC 4648) encoded 16 byte exposure key from the device.
-// - Base64 encoding should include padding, as per RFC 4648
-// - if the key is not exactly 16 bytes in length, the request will be failed
-// - that is, the whole batch will fail.
-// IntervalNumber must be "reasonable" as in the system won't accept keys that
-//   are scheduled to start in the future or that are too far in the past, which
-//   is configurable per installation.
-// IntervalCount must >= `minIntervalCount` and <= `maxIntervalCount`
-//   1 - 144 inclusive.
-// transmissionRisk must be >= 0 and <= 8.
-type ExposureKey struct {
-	Key              string `json:"key"`
-	IntervalNumber   int32  `json:"rollingStartNumber"`
-	IntervalCount    int32  `json:"rollingPeriod"`
-	TransmissionRisk int    `json:"transmissionRisk"`
-}
-
-// ExposureKeys represents a set of ExposureKey objects as input to
-// export file generation utility.
-// Keys: Required and must have length >= 1.
-type ExposureKeys struct {
-	Keys []ExposureKey `json:"temporaryExposureKeys"`
-}
-
 // Exposure represents the record as stored in the database
 // TODO(mikehelmick) - refactor this so that there is a public
 // Exposure struct that doesn't have public fields and an
@@ -163,7 +84,7 @@ type Exposure struct {
 // IntervalNumber calculates the exposure notification system interval
 // number based on the input time.
 func IntervalNumber(t time.Time) int32 {
-	return int32(t.UTC().Unix()) / int32(intervalLength.Seconds())
+	return int32(t.UTC().Unix()) / int32(verifyapi.IntervalLength.Seconds())
 }
 
 // TruncateWindow truncates a time based on the size of the creation window.
@@ -183,8 +104,8 @@ type Transformer struct {
 // records for insertion into the database. On the call to TransformPublish
 // all data is validated according to the transformer that is used.
 func NewTransformer(maxExposureKeys int, maxIntervalStartAge time.Duration, truncateWindow time.Duration, allowRestOfDay bool) (*Transformer, error) {
-	if maxExposureKeys < 0 || maxExposureKeys > maxKeysPerPublish {
-		return nil, fmt.Errorf("maxExposureKeys must be > 0 and <= %v, got %v", maxKeysPerPublish, maxExposureKeys)
+	if maxExposureKeys < 0 || maxExposureKeys > verifyapi.MaxKeysPerPublish {
+		return nil, fmt.Errorf("maxExposureKeys must be > 0 and <= %v, got %v", verifyapi.MaxKeysPerPublish, maxExposureKeys)
 	}
 	return &Transformer{
 		maxExposureKeys:     maxExposureKeys,
@@ -201,18 +122,18 @@ func NewTransformer(maxExposureKeys int, maxIntervalStartAge time.Duration, trun
 // * minInterval <= interval number <= maxInterval
 // * MinIntervalCount <= interval count <= MaxIntervalCount
 //
-func TransformExposureKey(exposureKey ExposureKey, appPackageName string, upcaseRegions []string, createdAt time.Time, minIntervalNumber, maxIntervalNumber int32) (*Exposure, error) {
+func TransformExposureKey(exposureKey verifyapi.ExposureKey, appPackageName string, upcaseRegions []string, createdAt time.Time, minIntervalNumber, maxIntervalNumber int32) (*Exposure, error) {
 	binKey, err := base64util.DecodeString(exposureKey.Key)
 	if err != nil {
 		return nil, err
 	}
 
 	// Validate individual pieces of the exposure key
-	if len(binKey) != KeyLength {
-		return nil, fmt.Errorf("invalid key length, %v, must be %v", len(binKey), KeyLength)
+	if len(binKey) != verifyapi.KeyLength {
+		return nil, fmt.Errorf("invalid key length, %v, must be %v", len(binKey), verifyapi.KeyLength)
 	}
-	if ic := exposureKey.IntervalCount; ic < MinIntervalCount || ic > MaxIntervalCount {
-		return nil, fmt.Errorf("invalid interval count, %v, must be >= %v && <= %v", ic, MinIntervalCount, MaxIntervalCount)
+	if ic := exposureKey.IntervalCount; ic < verifyapi.MinIntervalCount || ic > verifyapi.MaxIntervalCount {
+		return nil, fmt.Errorf("invalid interval count, %v, must be >= %v && <= %v", ic, verifyapi.MinIntervalCount, verifyapi.MaxIntervalCount)
 	}
 
 	// Validate the IntervalNumber.
@@ -229,8 +150,8 @@ func TransformExposureKey(exposureKey ExposureKey, appPackageName string, upcase
 			exposureKey.IntervalNumber, exposureKey.IntervalCount, maxIntervalNumber)
 	}
 
-	if tr := exposureKey.TransmissionRisk; tr < MinTransmissionRisk || tr > MaxTransmissionRisk {
-		return nil, fmt.Errorf("invalid transmission risk: %v, must be >= %v && <= %v", tr, MinTransmissionRisk, MaxTransmissionRisk)
+	if tr := exposureKey.TransmissionRisk; tr < verifyapi.MinTransmissionRisk || tr > verifyapi.MaxTransmissionRisk {
+		return nil, fmt.Errorf("invalid transmission risk: %v, must be >= %v && <= %v", tr, verifyapi.MinTransmissionRisk, verifyapi.MaxTransmissionRisk)
 	}
 
 	return &Exposure{
@@ -251,7 +172,7 @@ func TransformExposureKey(exposureKey ExposureKey, appPackageName string, upcase
 // * 0 exposure Keys in the requests
 // * > Transformer.maxExposureKeys in the request
 //
-func (t *Transformer) TransformPublish(inData *Publish, batchTime time.Time) ([]*Exposure, error) {
+func (t *Transformer) TransformPublish(inData *verifyapi.Publish, batchTime time.Time) ([]*Exposure, error) {
 	// Validate the number of keys that want to be published.
 	if len(inData.Keys) == 0 {
 		return nil, fmt.Errorf("no exposure keys in publish request")
diff --git a/internal/publish/publish.go b/internal/publish/publish.go
index 67c58c9..7789deb 100644
--- a/internal/publish/publish.go
+++ b/internal/publish/publish.go
@@ -32,6 +32,7 @@ import (
 	"github.com/google/exposure-notifications-server/internal/serverenv"
 	"github.com/google/exposure-notifications-server/internal/verification"
 	verifydb "github.com/google/exposure-notifications-server/internal/verification/database"
+	verifyapi "github.com/google/exposure-notifications-server/pkg/api/v1alpha1"
 )
 
 // NewHandler creates the HTTP handler for the TTK publishing API.
@@ -87,7 +88,7 @@ func (h *publishHandler) handleRequest(w http.ResponseWriter, r *http.Request) r
 	logger := logging.FromContext(ctx)
 	metrics := h.serverenv.MetricsExporter(ctx)
 
-	var data model.Publish
+	var data verifyapi.Publish
 	code, err := jsonutil.Unmarshal(w, r, &data)
 	if err != nil {
 		// Log the unparsable JSON, but return success to the client.
@@ -155,7 +156,7 @@ func (h *publishHandler) handleRequest(w http.ResponseWriter, r *http.Request) r
 
 	// Apply overrides
 	if len(overrides) > 0 {
-		data.ApplyTransmissionRiskOverrides(overrides)
+		model.ApplyTransmissionRiskOverrides(&data, overrides)
 	}
 
 	batchTime := time.Now()
diff --git a/internal/util/generate.go b/internal/util/generate.go
index f8ec5ff..270f39c 100644
--- a/internal/util/generate.go
+++ b/internal/util/generate.go
@@ -22,7 +22,7 @@ import (
 	"math/big"
 	"time"
 
-	"github.com/google/exposure-notifications-server/internal/publish/model"
+	"github.com/google/exposure-notifications-server/pkg/api/v1alpha1"
 	"github.com/google/exposure-notifications-server/testing/enclient"
 )
 
@@ -58,7 +58,7 @@ func RandomIntWithMin(min, max int) (int, error) {
 }
 
 func RandomTransmissionRisk() (int, error) {
-	n, err := RandomInt(model.MaxTransmissionRisk)
+	n, err := RandomInt(v1alpha1.MaxTransmissionRisk)
 	return n + 1, err
 }
 
@@ -70,7 +70,7 @@ func RandomArrValue(arr []string) (string, error) {
 	return arr[n], nil
 }
 
-func GenerateExposureKeys(numKeys, tr int, randomInterval bool) []model.ExposureKey {
+func GenerateExposureKeys(numKeys, tr int, randomInterval bool) []v1alpha1.ExposureKey {
 	// When publishing multiple keys - they'll be on different days.
 	var err error
 	intervalCount := int32(144)
@@ -83,7 +83,7 @@ func GenerateExposureKeys(numKeys, tr int, randomInterval bool) []model.Exposure
 	// Keys will normally align to UTC day boundries.
 	utcDay := time.Now().UTC().Truncate(24 * time.Hour)
 	intervalNumber := int32(utcDay.Unix()/600) - intervalCount
-	exposureKeys := make([]model.ExposureKey, numKeys)
+	exposureKeys := make([]v1alpha1.ExposureKey, numKeys)
 	for i := 0; i < numKeys; i++ {
 		transmissionRisk := tr
 		if transmissionRisk < 0 {
@@ -110,12 +110,12 @@ func GenerateExposureKeys(numKeys, tr int, randomInterval bool) []model.Exposure
 }
 
 // Creates a random exposure key.
-func RandomExposureKey(intervalNumber enclient.Interval, intervalCount int32, transmissionRisk int) (model.ExposureKey, error) {
+func RandomExposureKey(intervalNumber enclient.Interval, intervalCount int32, transmissionRisk int) (v1alpha1.ExposureKey, error) {
 	key, err := GenerateKey()
 	if err != nil {
-		return model.ExposureKey{}, err
+		return v1alpha1.ExposureKey{}, err
 	}
-	return model.ExposureKey{
+	return v1alpha1.ExposureKey{
 		Key:              key,
 		IntervalNumber:   int32(intervalNumber),
 		IntervalCount:    intervalCount,
diff --git a/internal/verification/phaverify.go b/internal/verification/phaverify.go
index 0650974..e030333 100644
--- a/internal/verification/phaverify.go
+++ b/internal/verification/phaverify.go
@@ -24,7 +24,6 @@ import (
 
 	aamodel "github.com/google/exposure-notifications-server/internal/authorizedapp/model"
 	"github.com/google/exposure-notifications-server/internal/base64util"
-	pubmodel "github.com/google/exposure-notifications-server/internal/publish/model"
 	"github.com/google/exposure-notifications-server/internal/verification/database"
 
 	verifyapi "github.com/google/exposure-notifications-server/pkg/api/v1alpha1"
@@ -46,7 +45,7 @@ func New(db *database.HealthAuthorityDB) *Verifier {
 // VerifyDiagnosisCertificate accepts a publish request (from which is extracts the JWT),
 // fully verifies the JWT and signture against what the passed in authorrized app is allowed
 // to use. Returns any transmission risk overrides if they are present.
-func (v *Verifier) VerifyDiagnosisCertificate(ctx context.Context, authApp *aamodel.AuthorizedApp, publish *pubmodel.Publish) (verifyapi.TransmissionRiskVector, error) {
+func (v *Verifier) VerifyDiagnosisCertificate(ctx context.Context, authApp *aamodel.AuthorizedApp, publish *verifyapi.Publish) (verifyapi.TransmissionRiskVector, error) {
 	// These get assigned during the ParseWithClaims closure.
 	var healthAuthorityID int64
 	var claims *verifyapi.VerificationClaims
diff --git a/pkg/api/v1alpha1/exposure_types.go b/pkg/api/v1alpha1/exposure_types.go
new file mode 100644
index 0000000..026b6ea
--- /dev/null
+++ b/pkg/api/v1alpha1/exposure_types.go
@@ -0,0 +1,98 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package v1alpha1
+
+import "time"
+
+// The following constants are generally useful in implementations of this API
+// and for clients as well..
+const (
+	// 15 Days worth of keys is the maximum per publish request (inclusive)
+	MaxKeysPerPublish = 15
+
+	// only valid exposure key keyLength
+	KeyLength = 16
+
+	// Transmission risk constraints (inclusive..inclusive)
+	MinTransmissionRisk = 0 // 0 indicates, no/unknown risk.
+	MaxTransmissionRisk = 8
+
+	// Intervals are defined as 10 minute periods, there are 144 of them in a day.
+	// IntervalCount constraints (inclusive..inclusive)
+	MinIntervalCount = 1
+	MaxIntervalCount = 144
+
+	// Self explanatory.
+	// oneDay = time.Hour * 24
+
+	// interval length
+	IntervalLength = 10 * time.Minute
+)
+
+// Publish represents the body of the PublishInfectedIds API call.
+// Keys: Required and must have length >= 1 and <= 21 (`maxKeysPerPublish`)
+// Regions: Array of regions. System defined, must match configuration.
+// AppPackageName: The identifier for the mobile application.
+//  - Android: The App Package AppPackageName
+//  - iOS: The BundleID
+// TransmissionRisk: An integer from 0-8 (inclusive) that represents
+//  the transmission risk for this publish.
+// Verification: The attestation payload for this request. (iOS or Android specific)
+//   Base64 encoded.
+// VerificationAuthorityName: a string that should be verified against the code provider.
+//  Note: This project doesn't directly include a diagnosis code verification System
+//        but does provide the ability to configure one in `serverevn.ServerEnv`
+//
+// The following fields are deprecated, but accepted for backwards-compatability:
+// DeviceVerificationPayload: (attestation)
+// Platform: "ios" or "android"
+type Publish struct {
+	Keys                []ExposureKey `json:"temporaryExposureKeys"`
+	Regions             []string      `json:"regions"`
+	AppPackageName      string        `json:"appPackageName"`
+	VerificationPayload string        `json:"verificationPayload"`
+	HMACKey             string        `json:"hmackey"`
+	Padding             string        `json:"padding"`
+
+	Platform                  string `json:"platform"`                  // DEPRECATED
+	DeviceVerificationPayload string `json:"deviceVerificationPayload"` // DEPRECATED
+}
+
+// ExposureKey is the 16 byte key, the start time of the key and the
+// duration of the key. A duration of 0 means 24 hours.
+// - ALL fields are REQUIRED and must meet the constraints below.
+// Key must be the base64 (RFC 4648) encoded 16 byte exposure key from the device.
+// - Base64 encoding should include padding, as per RFC 4648
+// - if the key is not exactly 16 bytes in length, the request will be failed
+// - that is, the whole batch will fail.
+// IntervalNumber must be "reasonable" as in the system won't accept keys that
+//   are scheduled to start in the future or that are too far in the past, which
+//   is configurable per installation.
+// IntervalCount must >= `minIntervalCount` and <= `maxIntervalCount`
+//   1 - 144 inclusive.
+// transmissionRisk must be >= 0 and <= 8.
+type ExposureKey struct {
+	Key              string `json:"key"`
+	IntervalNumber   int32  `json:"rollingStartNumber"`
+	IntervalCount    int32  `json:"rollingPeriod"`
+	TransmissionRisk int    `json:"transmissionRisk"`
+}
+
+// ExposureKeys represents a set of ExposureKey objects as input to
+// export file generation utility.
+// Keys: Required and must have length >= 1.
+type ExposureKeys struct {
+	Keys []ExposureKey `json:"temporaryExposureKeys"`
+}
diff --git a/pkg/verification/utils.go b/pkg/verification/utils.go
index 79d7367..c78b1c7 100644
--- a/pkg/verification/utils.go
+++ b/pkg/verification/utils.go
@@ -21,12 +21,12 @@ import (
 	"sort"
 	"strings"
 
-	"github.com/google/exposure-notifications-server/internal/publish/model"
+	verifyapi "github.com/google/exposure-notifications-server/pkg/api/v1alpha1"
 )
 
 // CalculateExposureKeyHMAC will calculate the verification protocol HMAC value.
 // Input keys are already to be base64 encoded. They will be sorted if necessary.
-func CalculateExposureKeyHMAC(keys []model.ExposureKey, secret []byte) ([]byte, error) {
+func CalculateExposureKeyHMAC(keys []verifyapi.ExposureKey, secret []byte) ([]byte, error) {
 	if len(keys) == 0 {
 		return nil, fmt.Errorf("cannot calculate hmac on empty exposure keys")
 	}
diff --git a/tools/export-generate/main.go b/tools/export-generate/main.go
index e10a6a3..bd07983 100644
--- a/tools/export-generate/main.go
+++ b/tools/export-generate/main.go
@@ -35,6 +35,8 @@ import (
 	"github.com/google/exposure-notifications-server/internal/export/model"
 
 	"github.com/google/exposure-notifications-server/internal/util"
+
+	verifyapi "github.com/google/exposure-notifications-server/pkg/api/v1alpha1"
 )
 
 var (
@@ -98,7 +100,7 @@ func main() {
 		if err != nil {
 			log.Fatalf("unable to read file: %v", err)
 		}
-		data := publishmodel.ExposureKeys{}
+		data := verifyapi.ExposureKeys{}
 		err = json.Unmarshal(file, &data)
 		if err != nil {
 			log.Fatalf("unable to parse json: %v", err)
@@ -128,7 +130,7 @@ func main() {
 				log.Fatalf("unable to decode key: %v", k.Key)
 			}
 			exposureKeys[i].ExposureKey = decoded
-			exposureKeys[i].IntervalNumber = k.IntervalNumber - publishmodel.MaxIntervalCount // typically the key will be at least 1 day old
+			exposureKeys[i].IntervalNumber = k.IntervalNumber - verifyapi.MaxIntervalCount // typically the key will be at least 1 day old
 			if exposureKeys[i].IntervalNumber < 0 {
 				exposureKeys[i].IntervalNumber = 0
 			}
diff --git a/tools/exposure-client/main.go b/tools/exposure-client/main.go
index c85c09c..885fbe7 100644
--- a/tools/exposure-client/main.go
+++ b/tools/exposure-client/main.go
@@ -24,8 +24,8 @@ import (
 	"strings"
 	"time"
 
-	"github.com/google/exposure-notifications-server/internal/publish/model"
 	"github.com/google/exposure-notifications-server/internal/util"
+	verifyapi "github.com/google/exposure-notifications-server/pkg/api/v1alpha1"
 	"github.com/google/exposure-notifications-server/testing/enclient"
 )
 
@@ -84,7 +84,7 @@ func main() {
 		log.Printf("could not get random padding: %v", err)
 	}
 
-	data := model.Publish{
+	data := verifyapi.Publish{
 		Keys:                exposureKeys,
 		Regions:             region,
 		AppPackageName:      *appPackage,


Test patch:
diff --git a/internal/export/worker_test.go b/internal/export/worker_test.go
index 02c947a..cb89139 100644
--- a/internal/export/worker_test.go
+++ b/internal/export/worker_test.go
@@ -20,23 +20,24 @@ import (
 	"testing"
 
 	"github.com/google/exposure-notifications-server/internal/publish/model"
+	verifyapi "github.com/google/exposure-notifications-server/pkg/api/v1alpha1"
 )
 
 func TestRandomInt(t *testing.T) {
 	expected := make(map[int]bool)
-	for i := model.MinTransmissionRisk; i <= model.MaxTransmissionRisk; i++ {
+	for i := verifyapi.MinTransmissionRisk; i <= verifyapi.MaxTransmissionRisk; i++ {
 		expected[i] = true
 	}
 
 	// Run through 1,000 iterations. To ensure the entire range can be hit.
 	for i := 0; i < 1000; i++ {
-		v, err := randomInt(model.MinTransmissionRisk, model.MaxTransmissionRisk)
+		v, err := randomInt(verifyapi.MinTransmissionRisk, verifyapi.MaxTransmissionRisk)
 		if err != nil {
 			t.Fatalf("error getting random data")
 		}
-		if v < model.MinTransmissionRisk || v > model.MaxTransmissionRisk {
+		if v < verifyapi.MinTransmissionRisk || v > verifyapi.MaxTransmissionRisk {
 			t.Fatalf("random data outside expected bounds. %v <= %v <= %v",
-				model.MinTransmissionRisk, v, model.MaxTransmissionRisk)
+				verifyapi.MinTransmissionRisk, v, verifyapi.MaxTransmissionRisk)
 		}
 		delete(expected, v)
 	}
@@ -57,7 +58,7 @@ func TestDoNotPadZeroLength(t *testing.T) {
 }
 
 func addExposure(t *testing.T, exposures []*model.Exposure, interval, count int32, risk int) []*model.Exposure {
-	key := make([]byte, model.KeyLength)
+	key := make([]byte, verifyapi.KeyLength)
 	_, err := rand.Read(key)
 	if err != nil {
 		t.Fatalf("error getting random data: %v", err)
@@ -73,7 +74,7 @@ func addExposure(t *testing.T, exposures []*model.Exposure, interval, count int3
 
 func TestEnsureMinExposures(t *testing.T) {
 	expectedTR := make(map[int]bool)
-	for i := model.MinTransmissionRisk; i <= model.MaxTransmissionRisk; i++ {
+	for i := verifyapi.MinTransmissionRisk; i <= verifyapi.MaxTransmissionRisk; i++ {
 		expectedTR[i] = true
 	}
 	// Insert a few exposures - that will be used to base the interval information off of.
diff --git a/internal/jsonutil/unmarshal_test.go b/internal/jsonutil/unmarshal_test.go
index ac60420..f2bb0ad 100644
--- a/internal/jsonutil/unmarshal_test.go
+++ b/internal/jsonutil/unmarshal_test.go
@@ -23,7 +23,8 @@ import (
 	"testing"
 	"time"
 
-	"github.com/google/exposure-notifications-server/internal/publish/model"
+	verifyapi "github.com/google/exposure-notifications-server/pkg/api/v1alpha1"
+
 	"github.com/google/go-cmp/cmp"
 )
 
@@ -33,7 +34,7 @@ func TestInvalidHeader(t *testing.T) {
 	r.Header.Set("content-type", "application/text")
 
 	w := httptest.NewRecorder()
-	data := &model.Publish{}
+	data := &verifyapi.Publish{}
 	code, err := Unmarshal(w, r, data)
 
 	expCode := http.StatusUnsupportedMediaType
@@ -126,7 +127,7 @@ func TestValidPublishMessage(t *testing.T) {
 
 	w := httptest.NewRecorder()
 
-	got := &model.Publish{}
+	got := &verifyapi.Publish{}
 	code, err := Unmarshal(w, r, got)
 	if err != nil {
 		t.Fatalf("unexpected err, %v", err)
@@ -135,8 +136,8 @@ func TestValidPublishMessage(t *testing.T) {
 		t.Errorf("unmarshal wanted %v response code, got %v", http.StatusOK, code)
 	}
 
-	want := &model.Publish{
-		Keys: []model.ExposureKey{
+	want := &verifyapi.Publish{
+		Keys: []verifyapi.ExposureKey{
 			{Key: "ABC", IntervalNumber: intervalNumber, IntervalCount: 144, TransmissionRisk: 2},
 			{Key: "DEF", IntervalNumber: intervalNumber, IntervalCount: 122, TransmissionRisk: 2},
 			{Key: "123", IntervalNumber: intervalNumber, IntervalCount: 1, TransmissionRisk: 2},
@@ -158,7 +159,7 @@ func unmarshalTestHelper(t *testing.T, payloads []string, errors []string, expCo
 		r.Header.Set("content-type", "application/json; charset=utf-8")
 
 		w := httptest.NewRecorder()
-		data := &model.Publish{}
+		data := &verifyapi.Publish{}
 		code, err := Unmarshal(w, r, data)
 		if code != expCode {
 			t.Errorf("unmarshal wanted %v response code, got %v", expCode, code)
diff --git a/internal/publish/model/exposure_model_test.go b/internal/publish/model/exposure_model_test.go
index 1e2f87a..06284ed 100644
--- a/internal/publish/model/exposure_model_test.go
+++ b/internal/publish/model/exposure_model_test.go
@@ -29,7 +29,7 @@ import (
 )
 
 func TestInvalidNew(t *testing.T) {
-	errMsg := fmt.Sprintf("maxExposureKeys must be > 0 and <= %v", maxKeysPerPublish)
+	errMsg := fmt.Sprintf("maxExposureKeys must be > 0 and <= %v", verifyapi.MaxKeysPerPublish)
 	cases := []struct {
 		maxKeys int
 		message string
@@ -37,9 +37,9 @@ func TestInvalidNew(t *testing.T) {
 		{0, errMsg},
 		{1, ""},
 		{5, ""},
-		{maxKeysPerPublish - 1, ""},
-		{maxKeysPerPublish, ""},
-		{maxKeysPerPublish + 1, errMsg},
+		{verifyapi.MaxKeysPerPublish - 1, ""},
+		{verifyapi.MaxKeysPerPublish, ""},
+		{verifyapi.MaxKeysPerPublish + 1, errMsg},
 	}
 
 	for i, c := range cases {
@@ -57,8 +57,8 @@ func TestInvalidBase64(t *testing.T) {
 	if err != nil {
 		t.Fatalf("error creating transformer: %v", err)
 	}
-	source := &Publish{
-		Keys: []ExposureKey{
+	source := &verifyapi.Publish{
+		Keys: []verifyapi.ExposureKey{
 			{
 				Key: base64.StdEncoding.EncodeToString([]byte("ABC")) + `2`,
 			},
@@ -112,21 +112,21 @@ func TestPublishValidation(t *testing.T) {
 
 	cases := []struct {
 		name    string
-		p       *Publish
+		p       *verifyapi.Publish
 		m       string
 		sameDay bool
 	}{
 		{
 			name: "no keys",
-			p: &Publish{
-				Keys: []ExposureKey{},
+			p: &verifyapi.Publish{
+				Keys: []verifyapi.ExposureKey{},
 			},
 			m: "no exposure keys in publish request",
 		},
 		{
 			name: "too many exposure keys",
-			p: &Publish{
-				Keys: []ExposureKey{
+			p: &verifyapi.Publish{
+				Keys: []verifyapi.ExposureKey{
 					{Key: "foo"},
 					{Key: "bar"},
 					{Key: "baz"},
@@ -136,73 +136,73 @@ func TestPublishValidation(t *testing.T) {
 		},
 		{
 			name: "transmission risk too low",
-			p: &Publish{
-				Keys: []ExposureKey{
+			p: &verifyapi.Publish{
+				Keys: []verifyapi.ExposureKey{
 					{
 						Key:              encodeKey(generateKey(t)),
 						IntervalNumber:   currentInterval - 2,
 						IntervalCount:    1,
-						TransmissionRisk: MinTransmissionRisk - 1,
+						TransmissionRisk: verifyapi.MinTransmissionRisk - 1,
 					},
 				},
 			},
-			m: fmt.Sprintf("invalid transmission risk: %v, must be >= %v && <= %v", MinTransmissionRisk-1, MinTransmissionRisk, MaxTransmissionRisk),
+			m: fmt.Sprintf("invalid transmission risk: %v, must be >= %v && <= %v", verifyapi.MinTransmissionRisk-1, verifyapi.MinTransmissionRisk, verifyapi.MaxTransmissionRisk),
 		},
 		{
 			name: "tranismission risk too high",
-			p: &Publish{
-				Keys: []ExposureKey{
+			p: &verifyapi.Publish{
+				Keys: []verifyapi.ExposureKey{
 					{
 						Key:              encodeKey(generateKey(t)),
 						IntervalNumber:   currentInterval - 2,
 						IntervalCount:    1,
-						TransmissionRisk: MaxTransmissionRisk + 1,
+						TransmissionRisk: verifyapi.MaxTransmissionRisk + 1,
 					},
 				},
 			},
-			m: fmt.Sprintf("invalid transmission risk: %v, must be >= %v && <= %v", MaxTransmissionRisk+1, MinTransmissionRisk, MaxTransmissionRisk),
+			m: fmt.Sprintf("invalid transmission risk: %v, must be >= %v && <= %v", verifyapi.MaxTransmissionRisk+1, verifyapi.MinTransmissionRisk, verifyapi.MaxTransmissionRisk),
 		},
 		{
 			name: "key length too short",
-			p: &Publish{
-				Keys: []ExposureKey{
-					{Key: encodeKey(generateKey(t)[0 : KeyLength-2])},
+			p: &verifyapi.Publish{
+				Keys: []verifyapi.ExposureKey{
+					{Key: encodeKey(generateKey(t)[0 : verifyapi.KeyLength-2])},
 				},
 			},
-			m: fmt.Sprintf("invalid key length, %v, must be %v", KeyLength-2, KeyLength),
+			m: fmt.Sprintf("invalid key length, %v, must be %v", verifyapi.KeyLength-2, verifyapi.KeyLength),
 		},
 		{
 			name: "interval count too small",
-			p: &Publish{
-				Keys: []ExposureKey{
+			p: &verifyapi.Publish{
+				Keys: []verifyapi.ExposureKey{
 					{
 						Key:           encodeKey(generateKey(t)),
-						IntervalCount: MinIntervalCount - 1,
+						IntervalCount: verifyapi.MinIntervalCount - 1,
 					},
 				},
 			},
-			m: fmt.Sprintf("invalid interval count, %v, must be >= %v && <= %v", MinIntervalCount-1, MinIntervalCount, MaxIntervalCount),
+			m: fmt.Sprintf("invalid interval count, %v, must be >= %v && <= %v", verifyapi.MinIntervalCount-1, verifyapi.MinIntervalCount, verifyapi.MaxIntervalCount),
 		},
 		{
 			name: "interval count too high",
-			p: &Publish{
-				Keys: []ExposureKey{
+			p: &verifyapi.Publish{
+				Keys: []verifyapi.ExposureKey{
 					{
 						Key:           encodeKey(generateKey(t)),
-						IntervalCount: MaxIntervalCount + 1,
+						IntervalCount: verifyapi.MaxIntervalCount + 1,
 					},
 				},
 			},
-			m: fmt.Sprintf("invalid interval count, %v, must be >= %v && <= %v", MaxIntervalCount+1, MinIntervalCount, MaxIntervalCount),
+			m: fmt.Sprintf("invalid interval count, %v, must be >= %v && <= %v", verifyapi.MaxIntervalCount+1, verifyapi.MinIntervalCount, verifyapi.MaxIntervalCount),
 		},
 		{
 			name: "interval number too low",
-			p: &Publish{
-				Keys: []ExposureKey{
+			p: &verifyapi.Publish{
+				Keys: []verifyapi.ExposureKey{
 					{
 						Key:            encodeKey(generateKey(t)),
 						IntervalNumber: minInterval - 1,
-						IntervalCount:  MaxIntervalCount,
+						IntervalCount:  verifyapi.MaxIntervalCount,
 					},
 				},
 			},
@@ -210,8 +210,8 @@ func TestPublishValidation(t *testing.T) {
 		},
 		{
 			name: "interval number too high",
-			p: &Publish{
-				Keys: []ExposureKey{
+			p: &verifyapi.Publish{
+				Keys: []verifyapi.ExposureKey{
 					{
 						Key:            encodeKey(generateKey(t)),
 						IntervalNumber: currentInterval + 1,
@@ -223,8 +223,8 @@ func TestPublishValidation(t *testing.T) {
 		},
 		{
 			name: "interval end too high",
-			p: &Publish{
-				Keys: []ExposureKey{
+			p: &verifyapi.Publish{
+				Keys: []verifyapi.ExposureKey{
 					{
 						Key:            encodeKey(generateKey(t)),
 						IntervalNumber: currentInterval - 143,
@@ -236,8 +236,8 @@ func TestPublishValidation(t *testing.T) {
 		},
 		{
 			name: "DEBUG: allow end of current UTC day still valid",
-			p: &Publish{
-				Keys: []ExposureKey{
+			p: &verifyapi.Publish{
+				Keys: []verifyapi.ExposureKey{
 					{
 						Key:            encodeKey(generateKey(t)),
 						IntervalNumber: IntervalNumber(captureStartTime.UTC().Truncate(24 * time.Hour)),
@@ -296,29 +296,29 @@ func TestTransform(t *testing.T) {
 	captureStartTime := time.Date(2020, 2, 29, 11, 15, 1, 0, time.UTC)
 	intervalNumber := IntervalNumber(captureStartTime)
 
-	source := &Publish{
-		Keys: []ExposureKey{
+	source := &verifyapi.Publish{
+		Keys: []verifyapi.ExposureKey{
 			{
 				Key:              encodeKey(generateKey(t)),
 				IntervalNumber:   intervalNumber,
-				IntervalCount:    MaxIntervalCount,
+				IntervalCount:    verifyapi.MaxIntervalCount,
 				TransmissionRisk: 1,
 			},
 			{
 				Key:              encodeKey(generateKey(t)),
-				IntervalNumber:   intervalNumber + MaxIntervalCount,
-				IntervalCount:    MaxIntervalCount,
+				IntervalNumber:   intervalNumber + verifyapi.MaxIntervalCount,
+				IntervalCount:    verifyapi.MaxIntervalCount,
 				TransmissionRisk: 2,
 			},
 			{
 				Key:              encodeKey(generateKey(t)),
-				IntervalNumber:   intervalNumber + 2*MaxIntervalCount,
-				IntervalCount:    MaxIntervalCount, // Invalid, should get rounded down
+				IntervalNumber:   intervalNumber + 2*verifyapi.MaxIntervalCount,
+				IntervalCount:    verifyapi.MaxIntervalCount, // Invalid, should get rounded down
 				TransmissionRisk: 3,
 			},
 			{
 				Key:              encodeKey(generateKey(t)),
-				IntervalNumber:   intervalNumber + 3*MaxIntervalCount,
+				IntervalNumber:   intervalNumber + 3*verifyapi.MaxIntervalCount,
 				IntervalCount:    42,
 				TransmissionRisk: 4,
 			},
@@ -332,24 +332,24 @@ func TestTransform(t *testing.T) {
 		{
 			ExposureKey:      decodeKey(source.Keys[0].Key, t),
 			IntervalNumber:   intervalNumber,
-			IntervalCount:    MaxIntervalCount,
+			IntervalCount:    verifyapi.MaxIntervalCount,
 			TransmissionRisk: 1,
 		},
 		{
 			ExposureKey:      decodeKey(source.Keys[1].Key, t),
-			IntervalNumber:   intervalNumber + MaxIntervalCount,
-			IntervalCount:    MaxIntervalCount,
+			IntervalNumber:   intervalNumber + verifyapi.MaxIntervalCount,
+			IntervalCount:    verifyapi.MaxIntervalCount,
 			TransmissionRisk: 2,
 		},
 		{
 			ExposureKey:      decodeKey(source.Keys[2].Key, t),
-			IntervalNumber:   intervalNumber + 2*MaxIntervalCount,
-			IntervalCount:    MaxIntervalCount,
+			IntervalNumber:   intervalNumber + 2*verifyapi.MaxIntervalCount,
+			IntervalCount:    verifyapi.MaxIntervalCount,
 			TransmissionRisk: 3,
 		},
 		{
 			ExposureKey:      decodeKey(source.Keys[3].Key, t),
-			IntervalNumber:   intervalNumber + 3*MaxIntervalCount,
+			IntervalNumber:   intervalNumber + 3*verifyapi.MaxIntervalCount,
 			IntervalCount:    42,
 			TransmissionRisk: 4,
 		},
@@ -392,21 +392,21 @@ func TestTransformOverlapping(t *testing.T) {
 
 	cases := []struct {
 		name   string
-		source Publish
+		source verifyapi.Publish
 	}{
 		{
 			name: "overlap",
-			source: Publish{
-				Keys: []ExposureKey{
+			source: verifyapi.Publish{
+				Keys: []verifyapi.ExposureKey{
 					{
 						Key:            encodeKey(generateKey(t)),
 						IntervalNumber: intervalNumber,
-						IntervalCount:  MaxIntervalCount,
+						IntervalCount:  verifyapi.MaxIntervalCount,
 					},
 					{
 						Key:            encodeKey(generateKey(t)),
-						IntervalNumber: intervalNumber + MaxIntervalCount - 2,
-						IntervalCount:  MaxIntervalCount,
+						IntervalNumber: intervalNumber + verifyapi.MaxIntervalCount - 2,
+						IntervalCount:  verifyapi.MaxIntervalCount,
 					},
 				},
 				Regions:        []string{"us", "cA", "Mx"}, // will be upcased
@@ -415,17 +415,17 @@ func TestTransformOverlapping(t *testing.T) {
 		},
 		{
 			name: "overlap 2",
-			source: Publish{
-				Keys: []ExposureKey{
+			source: verifyapi.Publish{
+				Keys: []verifyapi.ExposureKey{
 					{
 						Key:            encodeKey(generateKey(t)),
 						IntervalNumber: intervalNumber,
-						IntervalCount:  MaxIntervalCount,
+						IntervalCount:  verifyapi.MaxIntervalCount,
 					},
 					{
 						Key:            encodeKey(generateKey(t)),
-						IntervalNumber: intervalNumber - MaxIntervalCount + 1,
-						IntervalCount:  MaxIntervalCount,
+						IntervalNumber: intervalNumber - verifyapi.MaxIntervalCount + 1,
+						IntervalCount:  verifyapi.MaxIntervalCount,
 					},
 				},
 				Regions:        []string{"us", "cA", "Mx"}, // will be upcased
@@ -456,14 +456,14 @@ func TestTransformOverlapping(t *testing.T) {
 func TestApplyOverrides(t *testing.T) {
 	cases := []struct {
 		Name      string
-		Publish   Publish
+		Publish   verifyapi.Publish
 		Overrides verifyapi.TransmissionRiskVector
-		Want      []ExposureKey
+		Want      []verifyapi.ExposureKey
 	}{
 		{
 			Name: "no overrides",
-			Publish: Publish{
-				Keys: []ExposureKey{
+			Publish: verifyapi.Publish{
+				Keys: []verifyapi.ExposureKey{
 					{
 						Key:              "A",
 						IntervalNumber:   1,
@@ -473,7 +473,7 @@ func TestApplyOverrides(t *testing.T) {
 				},
 			},
 			Overrides: make([]verifyapi.TransmissionRiskOverride, 0),
-			Want: []ExposureKey{
+			Want: []verifyapi.ExposureKey{
 				{
 					Key:              "A",
 					IntervalNumber:   1,
@@ -484,8 +484,8 @@ func TestApplyOverrides(t *testing.T) {
 		},
 		{
 			Name: "aligned interval transmission risks",
-			Publish: Publish{
-				Keys: []ExposureKey{
+			Publish: verifyapi.Publish{
+				Keys: []verifyapi.ExposureKey{
 					{
 						Key:              "A",
 						IntervalNumber:   1,
@@ -520,7 +520,7 @@ func TestApplyOverrides(t *testing.T) {
 					TranismissionRisk:  0,
 				},
 			},
-			Want: []ExposureKey{
+			Want: []verifyapi.ExposureKey{
 				{
 					Key:              "A",
 					IntervalNumber:   1,
@@ -543,8 +543,8 @@ func TestApplyOverrides(t *testing.T) {
 		},
 		{
 			Name: "unaligned, with 0 fallback.",
-			Publish: Publish{
-				Keys: []ExposureKey{
+			Publish: verifyapi.Publish{
+				Keys: []verifyapi.ExposureKey{
 					{
 						Key:              "A",
 						IntervalNumber:   1,
@@ -575,7 +575,7 @@ func TestApplyOverrides(t *testing.T) {
 					TranismissionRisk:  2, // 2 since beginning of time.
 				},
 			},
-			Want: []ExposureKey{
+			Want: []verifyapi.ExposureKey{
 				{
 					Key:              "A",
 					IntervalNumber:   1,
@@ -598,8 +598,8 @@ func TestApplyOverrides(t *testing.T) {
 		},
 		{
 			Name: "overrides run out",
-			Publish: Publish{
-				Keys: []ExposureKey{
+			Publish: verifyapi.Publish{
+				Keys: []verifyapi.ExposureKey{
 					{
 						Key:              "A",
 						IntervalNumber:   1,
@@ -614,7 +614,7 @@ func TestApplyOverrides(t *testing.T) {
 					TranismissionRisk:  5, // anything effective at time >= 4 gets TR 5.
 				},
 			},
-			Want: []ExposureKey{
+			Want: []verifyapi.ExposureKey{
 				{
 					Key:              "A",
 					IntervalNumber:   1,
@@ -627,9 +627,9 @@ func TestApplyOverrides(t *testing.T) {
 
 	for _, tc := range cases {
 		t.Run(tc.Name, func(t *testing.T) {
-			tc.Publish.ApplyTransmissionRiskOverrides(tc.Overrides)
-			sorter := cmp.Transformer("Sort", func(in []ExposureKey) []ExposureKey {
-				out := append([]ExposureKey(nil), in...) // Copy input to avoid mutating it
+			ApplyTransmissionRiskOverrides(&tc.Publish, tc.Overrides)
+			sorter := cmp.Transformer("Sort", func(in []verifyapi.ExposureKey) []verifyapi.ExposureKey {
+				out := append([]verifyapi.ExposureKey(nil), in...) // Copy input to avoid mutating it
 				sort.Slice(out, func(i int, j int) bool {
 					return strings.Compare(out[i].Key, out[j].Key) <= 0
 				})
diff --git a/internal/publish/publish_test.go b/internal/publish/publish_test.go
index 813b698..fb390ce 100644
--- a/internal/publish/publish_test.go
+++ b/internal/publish/publish_test.go
@@ -84,7 +84,7 @@ func newSigningKey(t *testing.T) *signingKey {
 type jwtConfig struct {
 	HealthAuthority    *vermodel.HealthAuthority
 	HealthAuthorityKey *vermodel.HealthAuthorityKey
-	Publish            *model.Publish
+	Publish            *verifyapi.Publish
 	Key                *ecdsa.PrivateKey
 	JWTWarp            time.Duration
 	Overrides          verifyapi.TransmissionRiskVector
@@ -134,7 +134,7 @@ func TestPublishWithBypass(t *testing.T) {
 		HealthAuthority    *vermodel.HealthAuthority    // Automatically linked to keys.
 		HealthAuthorityKey *vermodel.HealthAuthorityKey // Automatically linked to SigningKey
 		AuthorizedApp      *aamodel.AuthorizedApp       // Automatically linked to health authorities.
-		Publish            model.Publish
+		Publish            verifyapi.Publish
 		JWTTiming          time.Duration
 		Overrides          verifyapi.TransmissionRiskVector
 		WantTRAdjustment   []int
@@ -150,7 +150,7 @@ func TestPublishWithBypass(t *testing.T) {
 				authApp.AllowedRegions["US"] = struct{}{}
 				return authApp
 			}(),
-			Publish: model.Publish{
+			Publish: verifyapi.Publish{
 				Keys:           util.GenerateExposureKeys(2, 5, false),
 				Regions:        []string{"US"},
 				AppPackageName: "com.example.health",
@@ -172,7 +172,7 @@ func TestPublishWithBypass(t *testing.T) {
 				authApp.AllowedRegions["US"] = struct{}{}
 				return authApp
 			}(),
-			Publish: model.Publish{
+			Publish: verifyapi.Publish{
 				Keys:           util.GenerateExposureKeys(2, 5, false),
 				Regions:        []string{"US"},
 				AppPackageName: "com.example.health.WRONG",
@@ -189,7 +189,7 @@ func TestPublishWithBypass(t *testing.T) {
 				authApp.AllowedRegions["US"] = struct{}{}
 				return authApp
 			}(),
-			Publish: model.Publish{
+			Publish: verifyapi.Publish{
 				Keys:           util.GenerateExposureKeys(2, 5, false),
 				Regions:        []string{"CA"},
 				AppPackageName: "com.example.health",
@@ -205,7 +205,7 @@ func TestPublishWithBypass(t *testing.T) {
 				authApp.AllowedRegions["US"] = struct{}{}
 				return authApp
 			}(),
-			Publish: model.Publish{
+			Publish: verifyapi.Publish{
 				Keys:                util.GenerateExposureKeys(2, 5, false),
 				Regions:             []string{"US"},
 				AppPackageName:      "com.example.health",
@@ -232,7 +232,7 @@ func TestPublishWithBypass(t *testing.T) {
 				authApp.AllowedRegions["US"] = struct{}{}
 				return authApp
 			}(),
-			Publish: model.Publish{
+			Publish: verifyapi.Publish{
 				Keys:                util.GenerateExposureKeys(2, 5, false),
 				Regions:             []string{"US"},
 				AppPackageName:      "com.example.health",
@@ -258,7 +258,7 @@ func TestPublishWithBypass(t *testing.T) {
 				authApp.AllowedRegions["US"] = struct{}{}
 				return authApp
 			}(),
-			Publish: model.Publish{
+			Publish: verifyapi.Publish{
 				Keys:                util.GenerateExposureKeys(2, 5, false),
 				Regions:             []string{"US"},
 				AppPackageName:      "com.example.health",
@@ -291,7 +291,7 @@ func TestPublishWithBypass(t *testing.T) {
 				authApp.AllowedRegions["US"] = struct{}{}
 				return authApp
 			}(),
-			Publish: model.Publish{
+			Publish: verifyapi.Publish{
 				Keys:                util.GenerateExposureKeys(2, 5, false),
 				Regions:             []string{"US"},
 				AppPackageName:      "com.example.health",
@@ -319,7 +319,7 @@ func TestPublishWithBypass(t *testing.T) {
 				authApp.AllowedRegions["US"] = struct{}{}
 				return authApp
 			}(),
-			Publish: model.Publish{
+			Publish: verifyapi.Publish{
 				Keys:                util.GenerateExposureKeys(2, 5, false),
 				Regions:             []string{"US"},
 				AppPackageName:      "com.example.health",
diff --git a/internal/verification/phaverify_test.go b/internal/verification/phaverify_test.go
index 10c626d..1a236a8 100644
--- a/internal/verification/phaverify_test.go
+++ b/internal/verification/phaverify_test.go
@@ -33,7 +33,6 @@ import (
 
 	aamodel "github.com/google/exposure-notifications-server/internal/authorizedapp/model"
 	coredb "github.com/google/exposure-notifications-server/internal/database"
-	pubmodel "github.com/google/exposure-notifications-server/internal/publish/model"
 	"github.com/google/exposure-notifications-server/internal/verification/database"
 	"github.com/google/exposure-notifications-server/internal/verification/model"
 
@@ -132,8 +131,8 @@ func TestVerifyCertificate(t *testing.T) {
 			authApp.AllowedHealthAuthorityIDs[healthAuthority.ID] = struct{}{}
 
 			// Build a sample certificate.
-			publish := pubmodel.Publish{
-				Keys: []pubmodel.ExposureKey{
+			publish := verifyapi.Publish{
+				Keys: []verifyapi.ExposureKey{
 					{
 						Key:              "IRgYIhYiy4WMl9z68bMk6w==",
 						IntervalNumber:   2650032,
diff --git a/pkg/verification/utils_test.go b/pkg/verification/utils_test.go
index 6079374..45c6c11 100644
--- a/pkg/verification/utils_test.go
+++ b/pkg/verification/utils_test.go
@@ -18,13 +18,13 @@ import (
 	"encoding/base64"
 	"testing"
 
-	"github.com/google/exposure-notifications-server/internal/publish/model"
+	verifyapi "github.com/google/exposure-notifications-server/pkg/api/v1alpha1"
 )
 
 func TestCalculateHac(t *testing.T) {
 	secret := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
 
-	eKeys := []model.ExposureKey{
+	eKeys := []verifyapi.ExposureKey{
 		{
 			Key:              "z2Cx9hdz2SlxZ8GEgqTYpA==",
 			IntervalNumber:   1,
diff --git a/testing/enclient/enclient.go b/testing/enclient/enclient.go
index b0ce497..63d61d2 100644
--- a/testing/enclient/enclient.go
+++ b/testing/enclient/enclient.go
@@ -9,7 +9,7 @@ import (
 	"net/http"
 	"time"
 
-	"github.com/google/exposure-notifications-server/internal/publish/model"
+	verifyapi "github.com/google/exposure-notifications-server/pkg/api/v1alpha1"
 )
 
 const (
@@ -67,8 +67,8 @@ func NewInterval(time int64) Interval {
 }
 
 // Creates an exposure key.
-func ExposureKey(key string, intervalNumber Interval, intervalCount int32, transmissionRisk int) model.ExposureKey {
-	return model.ExposureKey{
+func ExposureKey(key string, intervalNumber Interval, intervalCount int32, transmissionRisk int) verifyapi.ExposureKey {
+	return verifyapi.ExposureKey{
 		Key:              key,
 		IntervalNumber:   int32(intervalNumber),
 		IntervalCount:    intervalCount,