Repository: PagerDuty/go-pagerduty
Pull request #406 (2021-12-06 05:23:02)

add createStatusUpdate
API has the action to create status updates. But `go-pagerduty` does not have the function for this yet.
I add some new functions to solve this issue.
https://developer.pagerduty.com/api-reference/b3A6Mjc0ODE1Mw-create-a-status-update-on-an-incident


Patch:
diff --git a/.gitignore b/.gitignore
index 5162bc9..47423b1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,3 @@ pd
 vendor/
 .vscode/
 .idea/
-
-# development API key(s)
-*.key
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2a1e2c4..c3a6033 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,19 +1,5 @@
 # Changelog
 
-## [v1.4.3](https://github.com/PagerDuty/go-pagerduty/tree/v1.4.3) (2021-11-13)
-
-[Milestone](https://github.com/PagerDuty/go-pagerduty/milestone/6)
-[Full Changelog](https://github.com/PagerDuty/go-pagerduty/compare/v1.4.1...v1.4.3)
-
-**Highlights**
-- Mitigate PagerDuty REST API bug that would result in a JSON parsing failure when reading an error response from the API. Prior to `v1.4.0` our error parsing logic was not impacted by the bug.
-
-**Merged pull requests**
-- Handle unexpected type changes in PagerDuty REST API error responses [\#382](https://github.com/PagerDuty/go-pagerduty/pull/382) [backported via [\#386](https://github.com/PagerDuty/go-pagerduty/pull/386)] ([theckman](https://github.com/theckman))
-
-**Closed issues**
-- APIError unmarshaling broken in 1.4 [\#339](https://github.com/PagerDuty/go-pagerduty/pull/339) ([dsymonds](https://github.com/dsymonds))
-
 ## [v1.4.2](https://github.com/PagerDuty/go-pagerduty/tree/v1.4.2) (2021-08-30)
 
 [Milestone](https://github.com/PagerDuty/go-pagerduty/milestone/5)  
diff --git a/analytics.go b/analytics.go
index ea6ce15..bb1a2dc 100644
--- a/analytics.go
+++ b/analytics.go
@@ -2,11 +2,8 @@ package pagerduty
 
 import (
 	"context"
-	"fmt"
 )
 
-const analyticsBaseURL = "/analytics/metrics/incidents"
-
 // AnalyticsRequest represents the request to be sent to PagerDuty when you want
 // aggregated analytics.
 type AnalyticsRequest struct {
@@ -63,26 +60,48 @@ type AnalyticsData struct {
 
 // GetAggregatedIncidentData gets the aggregated incident analytics for the requested data.
 func (c *Client) GetAggregatedIncidentData(ctx context.Context, analytics AnalyticsRequest) (AnalyticsResponse, error) {
-	return c.getAggregatedData(ctx, analytics, "all")
+	h := map[string]string{
+		"X-EARLY-ACCESS": "analytics-v2",
+	}
+	resp, err := c.post(ctx, "/analytics/metrics/incidents/all", analytics, h)
+	if err != nil {
+		return AnalyticsResponse{}, err
+	}
+
+	var analyticsResponse AnalyticsResponse
+	if err = c.decodeJSON(resp, &analyticsResponse); err != nil {
+		return AnalyticsResponse{}, err
+	}
+
+	return analyticsResponse, nil
 }
 
 // GetAggregatedServiceData gets the aggregated service analytics for the requested data.
 func (c *Client) GetAggregatedServiceData(ctx context.Context, analytics AnalyticsRequest) (AnalyticsResponse, error) {
-	return c.getAggregatedData(ctx, analytics, "services")
+	h := map[string]string{
+		"X-EARLY-ACCESS": "analytics-v2",
+	}
+
+	resp, err := c.post(ctx, "/analytics/metrics/incidents/services", analytics, h)
+	if err != nil {
+		return AnalyticsResponse{}, err
+	}
+
+	var analyticsResponse AnalyticsResponse
+	if err = c.decodeJSON(resp, &analyticsResponse); err != nil {
+		return AnalyticsResponse{}, err
+	}
+
+	return analyticsResponse, nil
 }
 
 // GetAggregatedTeamData gets the aggregated team analytics for the requested data.
 func (c *Client) GetAggregatedTeamData(ctx context.Context, analytics AnalyticsRequest) (AnalyticsResponse, error) {
-	return c.getAggregatedData(ctx, analytics, "teams")
-}
-
-func (c *Client) getAggregatedData(ctx context.Context, analytics AnalyticsRequest, endpoint string) (AnalyticsResponse, error) {
 	h := map[string]string{
 		"X-EARLY-ACCESS": "analytics-v2",
 	}
 
-	u := fmt.Sprintf("%s/%s", analyticsBaseURL, endpoint)
-	resp, err := c.post(ctx, u, analytics, h)
+	resp, err := c.post(ctx, "/analytics/metrics/incidents/teams", analytics, h)
 	if err != nil {
 		return AnalyticsResponse{}, err
 	}
diff --git a/change_events.go b/change_events.go
index 3dcf97d..a2b30f4 100644
--- a/change_events.go
+++ b/change_events.go
@@ -15,23 +15,23 @@ const changeEventPath = "/v2/change/enqueue"
 type ChangeEvent struct {
 	RoutingKey string             `json:"routing_key"`
 	Payload    ChangeEventPayload `json:"payload"`
-	Links      []ChangeEventLink  `json:"links,omitempty"`
+	Links      []ChangeEventLink  `json:"links"`
 }
 
 // ChangeEventPayload ChangeEvent ChangeEventPayload
 // https://developer.pagerduty.com/docs/events-api-v2/send-change-events/#example-request-payload
 type ChangeEventPayload struct {
+	Source        string                 `json:"source"`
 	Summary       string                 `json:"summary"`
-	Source        string                 `json:"source,omitempty"`
-	Timestamp     string                 `json:"timestamp,omitempty"`
-	CustomDetails map[string]interface{} `json:"custom_details,omitempty"`
+	Timestamp     string                 `json:"timestamp"`
+	CustomDetails map[string]interface{} `json:"custom_details"`
 }
 
 // ChangeEventLink represents a single link in a ChangeEvent
 // https://developer.pagerduty.com/docs/events-api-v2/send-change-events/#the-links-property
 type ChangeEventLink struct {
 	Href string `json:"href"`
-	Text string `json:"text,omitempty"`
+	Text string `json:"text"`
 }
 
 // ChangeEventResponse is the json response body for an event
diff --git a/client.go b/client.go
index 0c5adcf..abfb849 100644
--- a/client.go
+++ b/client.go
@@ -83,16 +83,6 @@ type APIErrorObject struct {
 	Errors  []string `json:"errors,omitempty"`
 }
 
-// fallbackAPIErrorObject is a shim to solve this issue:
-// https://github.com/PagerDuty/go-pagerduty/issues/339
-//
-// TODO: remove when PagerDuty engineering confirms bugfix to the REST API
-type fallbackAPIErrorObject struct {
-	Code    int    `json:"code,omitempty"`
-	Message string `json:"message,omitempty"`
-	Errors  string `json:"errors,omitempty"`
-}
-
 // NullAPIErrorObject is a wrapper around the APIErrorObject type. If the Valid
 // field is true, the API response included a structured error JSON object. This
 // structured object is then set on the ErrorObject field.
@@ -100,7 +90,7 @@ type fallbackAPIErrorObject struct {
 // While the PagerDuty REST API is documented to always return the error object,
 // we assume it's possible in exceptional failure modes for this to be omitted.
 // As such, this wrapper type provides us a way to check if the object was
-// provided while avoiding consumers accidentally missing a nil pointer check,
+// provided while avoiding cosnumers accidentally missing a nil pointer check,
 // thus crashing their whole program.
 type NullAPIErrorObject struct {
 	Valid       bool
@@ -110,27 +100,8 @@ type NullAPIErrorObject struct {
 // UnmarshalJSON satisfies encoding/json.Unmarshaler
 func (n *NullAPIErrorObject) UnmarshalJSON(data []byte) error {
 	var aeo APIErrorObject
-
-	err := json.Unmarshal(data, &aeo)
-	if err != nil {
-		terr, ok := err.(*json.UnmarshalTypeError)
-		if !ok {
-			return err
-		}
-
-		//
-		// see https://github.com/PagerDuty/go-pagerduty/issues/339
-		//
-		var faeo fallbackAPIErrorObject
-
-		if err := json.Unmarshal(data, &faeo); err != nil {
-			// still failed, so return the original error
-			return terr
-		}
-
-		aeo.Code = faeo.Code
-		aeo.Message = faeo.Message
-		aeo.Errors = []string{faeo.Errors}
+	if err := json.Unmarshal(data, &aeo); err != nil {
+		return err
 	}
 
 	n.ErrorObject = aeo
diff --git a/escalation_policy.go b/escalation_policy.go
index 2f3febd..8dac7a3 100644
--- a/escalation_policy.go
+++ b/escalation_policy.go
@@ -28,6 +28,7 @@ type EscalationPolicy struct {
 	NumLoops        uint             `json:"num_loops,omitempty"`
 	Teams           []APIReference   `json:"teams"`
 	Description     string           `json:"description,omitempty"`
+	RepeatEnabled   bool             `json:"repeat_enabled,omitempty"`
 }
 
 // ListEscalationPoliciesResponse is the data structure returned from calling the ListEscalationPolicies API endpoint.
diff --git a/incident.go b/incident.go
index 18b8233..e6e1920 100644
--- a/incident.go
+++ b/incident.go
@@ -114,15 +114,8 @@ type ListIncidentsOptions struct {
 
 // ConferenceBridge is a struct for the conference_bridge object on an incident
 type ConferenceBridge struct {
-	// ConferenceNumber is the phone number of the conference call for the
-	// conference bridge. Phone numbers should be formatted like
-	// +1 415-555-1212,,,,1234#, where a comma (,) represents a one-second
-	// wait and pound (#) completes access code input.
 	ConferenceNumber string `json:"conference_number,omitempty"`
-
-	// ConferenceURL is the URL for the conference bridge. This could be a link
-	// to a video conference or Slack channel.
-	ConferenceURL string `json:"conference_url,omitempty"`
+	ConferenceURL    string `json:"conference_url,omitempty"`
 }
 
 // ListIncidents lists existing incidents.
@@ -159,44 +152,27 @@ type createIncidentResponse struct {
 
 // CreateIncidentOptions is the structure used when POSTing to the CreateIncident API endpoint.
 type CreateIncidentOptions struct {
-	// Type is the type of API object this is.
-	//
-	// Deprecated: Because the Type field can only have the value of "incident"
-	// when creating an incident, the CreateIncident* methods always set this
-	// value to "incident". Any other value will be overwritten. This will be
-	// removed in v2.0.0.
-	Type             string            `json:"type"`
-	Title            string            `json:"title"`
-	Service          *APIReference     `json:"service"`
-	Priority         *APIReference     `json:"priority"`
-	Urgency          string            `json:"urgency,omitempty"`
-	IncidentKey      string            `json:"incident_key,omitempty"`
-	Body             *APIDetails       `json:"body,omitempty"`
-	EscalationPolicy *APIReference     `json:"escalation_policy,omitempty"`
-	Assignments      []Assignee        `json:"assignments,omitempty"`
-	ConferenceBridge *ConferenceBridge `json:"conference_bridge,omitempty"`
+	Type             string        `json:"type"`
+	Title            string        `json:"title"`
+	Service          *APIReference `json:"service"`
+	Priority         *APIReference `json:"priority"`
+	Urgency          string        `json:"urgency,omitempty"`
+	IncidentKey      string        `json:"incident_key,omitempty"`
+	Body             *APIDetails   `json:"body,omitempty"`
+	EscalationPolicy *APIReference `json:"escalation_policy,omitempty"`
+	Assignments      []Assignee    `json:"assignments,omitempty"`
 }
 
 // ManageIncidentsOptions is the structure used when PUTing updates to incidents to the ManageIncidents func
 type ManageIncidentsOptions struct {
-	ID string `json:"id"`
-
-	// Type is the type of API object this is.
-	//
-	// Deprecated: Because the Type field can only have the value of "incident"
-	// or "incident_reference" when managing an incident, the CreateIncident*
-	// methods always set this value to "incident" because this struct is not an
-	// incident_reference. Any other value will be overwritten. This will be
-	// removed in v2.0.0.
-	Type             string            `json:"type"`
-	Status           string            `json:"status,omitempty"`
-	Title            string            `json:"title,omitempty"`
-	Priority         *APIReference     `json:"priority,omitempty"`
-	Assignments      []Assignee        `json:"assignments,omitempty"`
-	EscalationLevel  uint              `json:"escalation_level,omitempty"`
-	EscalationPolicy *APIReference     `json:"escalation_policy,omitempty"`
-	Resolution       string            `json:"resolution,omitempty"`
-	ConferenceBridge *ConferenceBridge `json:"conference_bridge,omitempty"`
+	ID               string        `json:"id"`
+	Type             string        `json:"type"`
+	Status           string        `json:"status,omitempty"`
+	Title            string        `json:"title,omitempty"`
+	Priority         *APIReference `json:"priority,omitempty"`
+	Assignments      []Assignee    `json:"assignments,omitempty"`
+	EscalationPolicy *APIReference `json:"escalation_policy,omitempty"`
+	Resolution       string        `json:"resolution,omitempty"`
 }
 
 // MergeIncidentsOptions is the structure used when merging incidents with MergeIncidents func
@@ -220,9 +196,6 @@ func (c *Client) CreateIncidentWithContext(ctx context.Context, from string, o *
 		"From": from,
 	}
 
-	// see: https://github.com/PagerDuty/go-pagerduty/issues/390
-	o.Type = "incident"
-
 	d := map[string]*CreateIncidentOptions{
 		"incident": o,
 	}
@@ -251,11 +224,6 @@ func (c *Client) ManageIncidents(from string, incidents []ManageIncidentsOptions
 // ManageIncidentsWithContext acknowledges, resolves, escalates, or reassigns
 // one or more incidents.
 func (c *Client) ManageIncidentsWithContext(ctx context.Context, from string, incidents []ManageIncidentsOptions) (*ListIncidentsResponse, error) {
-	// see: https://github.com/PagerDuty/go-pagerduty/issues/390
-	for i := range incidents {
-		incidents[i].Type = "incident"
-	}
-
 	d := map[string][]ManageIncidentsOptions{
 		"incidents": incidents,
 	}
@@ -664,12 +632,15 @@ func (c *Client) GetIncidentAlertWithContext(ctx context.Context, incidentID, al
 }
 
 // ManageIncidentAlerts allows you to manage the alerts of an incident.
-func (c *Client) ManageIncidentAlerts(ctx context.Context, incidentID, from string, alerts *IncidentAlertList) (*ListAlertsResponse, error) {
-	h := map[string]string{
-		"From": from,
-	}
+//
+// Deprecated: Use ManageIncidentAlertsWithContext instead.
+func (c *Client) ManageIncidentAlerts(incidentID string, alerts *IncidentAlertList) (*ListAlertsResponse, error) {
+	return c.ManageIncidentAlertsWithContext(context.Background(), incidentID, alerts)
+}
 
-	resp, err := c.put(ctx, "/incidents/"+incidentID+"/alerts/", alerts, h)
+// ManageIncidentAlertsWithContext allows you to manage the alerts of an incident.
+func (c *Client) ManageIncidentAlertsWithContext(ctx context.Context, incidentID string, alerts *IncidentAlertList) (*ListAlertsResponse, error) {
+	resp, err := c.put(ctx, "/incidents/"+incidentID+"/alerts/", alerts, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -682,4 +653,35 @@ func (c *Client) ManageIncidentAlerts(ctx context.Context, incidentID, from stri
 	return &result, nil
 }
 
-/* TODO: Create Status Updates */
+// IncidentStatusUpdate is a status update for the specified incident.
+type IncidentStatusUpdate struct {
+	ID        string	`json:"id"`
+	Message   string	`json:"message"`
+	CreatedAt string	`json:"created_at"`
+	Sender    APIObject	`json:"sender"`
+}
+
+// CreateIncidentStatusUpdate creates a new status update for the specified incident.
+func (c *Client) CreateIncidentStatusUpdate(ctx context.Context, id string, from string, message string) (IncidentStatusUpdate, error) {
+	d := map[string]string{
+		"message": message,
+	}
+
+	h := map[string]string{
+		"From": from,
+	}
+
+	resp, err := c.post(ctx, "/incidents/"+id+"/status_updates", d, h)
+	if err != nil {
+		return IncidentStatusUpdate{}, err
+	}
+
+	var result struct {
+		IncidentStatusUpdate IncidentStatusUpdate `json:"status_update"`
+	}
+	if err = c.decodeJSON(resp, &result); err != nil {
+		return IncidentStatusUpdate{}, err
+	}
+
+	return result.IncidentStatusUpdate, nil
+}
diff --git a/log_entry.go b/log_entry.go
index 1585e4c..00459e5 100644
--- a/log_entry.go
+++ b/log_entry.go
@@ -36,7 +36,6 @@ type CommonLogEntryField struct {
 	Contexts               []Context         `json:"contexts,omitempty"`
 	AcknowledgementTimeout int               `json:"acknowledgement_timeout"`
 	EventDetails           map[string]string `json:"event_details,omitempty"`
-	Assignees              []APIObject       `json:"assignees,omitempty"`
 }
 
 // LogEntry is a list of all of the events that happened to an incident.
diff --git a/service.go b/service.go
index 1547fac..72582c1 100644
--- a/service.go
+++ b/service.go
@@ -8,6 +8,17 @@ import (
 	"github.com/google/go-querystring/query"
 )
 
+// Integration is an endpoint (like Nagios, email, or an API call) that generates events, which are normalized and de-duplicated by PagerDuty to create incidents.
+type Integration struct {
+	APIObject
+	Name             string     `json:"name,omitempty"`
+	Service          *APIObject `json:"service,omitempty"`
+	CreatedAt        string     `json:"created_at,omitempty"`
+	Vendor           *APIObject `json:"vendor,omitempty"`
+	IntegrationKey   string     `json:"integration_key,omitempty"`
+	IntegrationEmail string     `json:"integration_email,omitempty"`
+}
+
 // InlineModel represents when a scheduled action will occur.
 type InlineModel struct {
 	Type string `json:"type,omitempty"`
@@ -80,8 +91,8 @@ type Service struct {
 	APIObject
 	Name                    string                   `json:"name,omitempty"`
 	Description             string                   `json:"description,omitempty"`
-	AutoResolveTimeout      *uint                    `json:"auto_resolve_timeout,omitempty"`
-	AcknowledgementTimeout  *uint                    `json:"acknowledgement_timeout,omitempty"`
+	AutoResolveTimeout      *uint                    `json:"auto_resolve_timeout"`
+	AcknowledgementTimeout  *uint                    `json:"acknowledgement_timeout"`
 	CreateAt                string                   `json:"created_at,omitempty"`
 	Status                  string                   `json:"status,omitempty"`
 	LastIncidentTimestamp   string                   `json:"last_incident_timestamp,omitempty"`
@@ -89,8 +100,8 @@ type Service struct {
 	EscalationPolicy        EscalationPolicy         `json:"escalation_policy,omitempty"`
 	Teams                   []Team                   `json:"teams,omitempty"`
 	IncidentUrgencyRule     *IncidentUrgencyRule     `json:"incident_urgency_rule,omitempty"`
-	SupportHours            *SupportHours            `json:"support_hours,omitempty"`
-	ScheduledActions        []ScheduledAction        `json:"scheduled_actions,omitempty"`
+	SupportHours            *SupportHours            `json:"support_hours"`
+	ScheduledActions        []ScheduledAction        `json:"scheduled_actions"`
 	AlertCreation           string                   `json:"alert_creation,omitempty"`
 	AlertGrouping           string                   `json:"alert_grouping,omitempty"`
 	AlertGroupingTimeout    *uint                    `json:"alert_grouping_timeout,omitempty"`
@@ -254,6 +265,72 @@ func (c *Client) DeleteServiceWithContext(ctx context.Context, id string) error
 	return err
 }
 
+// CreateIntegration creates a new integration belonging to a service.
+//
+// Deprecated: Use CreateIntegrationWithContext instead.
+func (c *Client) CreateIntegration(id string, i Integration) (*Integration, error) {
+	return c.CreateIntegrationWithContext(context.Background(), id, i)
+}
+
+// CreateIntegrationWithContext creates a new integration belonging to a service.
+func (c *Client) CreateIntegrationWithContext(ctx context.Context, id string, i Integration) (*Integration, error) {
+	d := map[string]Integration{
+		"integration": i,
+	}
+
+	resp, err := c.post(ctx, "/services/"+id+"/integrations", d, nil)
+	return getIntegrationFromResponse(c, resp, err)
+}
+
+// GetIntegrationOptions is the data structure used when calling the GetIntegration API endpoint.
+type GetIntegrationOptions struct {
+	Includes []string `url:"include,omitempty,brackets"`
+}
+
+// GetIntegration gets details about an integration belonging to a service.
+//
+// Deprecated: Use GetIntegrationWithContext instead.
+func (c *Client) GetIntegration(serviceID, integrationID string, o GetIntegrationOptions) (*Integration, error) {
+	return c.GetIntegrationWithContext(context.Background(), serviceID, integrationID, o)
+}
+
+// GetIntegrationWithContext gets details about an integration belonging to a service.
+func (c *Client) GetIntegrationWithContext(ctx context.Context, serviceID, integrationID string, o GetIntegrationOptions) (*Integration, error) {
+	v, err := query.Values(o)
+	if err != nil {
+		return nil, err
+	}
+
+	resp, err := c.get(ctx, "/services/"+serviceID+"/integrations/"+integrationID+"?"+v.Encode())
+	return getIntegrationFromResponse(c, resp, err)
+}
+
+// UpdateIntegration updates an integration belonging to a service.
+//
+// Deprecated: Use UpdateIntegrationWithContext instead.
+func (c *Client) UpdateIntegration(serviceID string, i Integration) (*Integration, error) {
+	return c.UpdateIntegrationWithContext(context.Background(), serviceID, i)
+}
+
+// UpdateIntegrationWithContext updates an integration belonging to a service.
+func (c *Client) UpdateIntegrationWithContext(ctx context.Context, serviceID string, i Integration) (*Integration, error) {
+	resp, err := c.put(ctx, "/services/"+serviceID+"/integrations/"+i.ID, i, nil)
+	return getIntegrationFromResponse(c, resp, err)
+}
+
+// DeleteIntegration deletes an existing integration.
+//
+// Deprecated: Use DeleteIntegrationWithContext instead.
+func (c *Client) DeleteIntegration(serviceID string, integrationID string) error {
+	return c.DeleteIntegrationWithContext(context.Background(), serviceID, integrationID)
+}
+
+// DeleteIntegrationWithContext deletes an existing integration.
+func (c *Client) DeleteIntegrationWithContext(ctx context.Context, serviceID string, integrationID string) error {
+	_, err := c.delete(ctx, "/services/"+serviceID+"/integrations/"+integrationID)
+	return err
+}
+
 // ListServiceRulesPaginated gets all rules for a service.
 func (c *Client) ListServiceRulesPaginated(ctx context.Context, serviceID string) ([]ServiceRule, error) {
 	var rules []ServiceRule
diff --git a/service_integration.go b/service_integration.go
deleted file mode 100644
index c1068b4..0000000
--- a/service_integration.go
+++ /dev/null
@@ -1,354 +0,0 @@
-package pagerduty
-
-import (
-	"bytes"
-	"context"
-	"encoding/json"
-	"errors"
-	"fmt"
-
-	"github.com/google/go-querystring/query"
-)
-
-// IntegrationEmailFilterMode is a type to respresent the different filter modes
-// for a Generic Email Integration. This defines how the email filter rules
-// (IntegrationEmailFilterRuleMode) are used when emails are ingested.
-type IntegrationEmailFilterMode uint8
-
-const (
-	// EmailFilterModeInvalid only exists to make it harder to use values of
-	// this type incorrectly. Please instead use one of EmailFilterModeAll,
-	// EmailFilterModeOr, EmailFilterModeAnd
-	//
-	// This value should not get marshaled to JSON by the encoding/json package.
-	EmailFilterModeInvalid IntegrationEmailFilterMode = iota
-
-	// EmailFilterModeAll means that all incoming email will be be accepted, and
-	// no email rules will be considered.
-	EmailFilterModeAll
-
-	// EmailFilterModeOr instructs the email filtering system to accept the
-	// email if one or more rules match the message.
-	EmailFilterModeOr
-
-	// EmailFilterModeAnd instructs the email filtering system to accept the
-	// email only if all of the rules match the message.
-	EmailFilterModeAnd
-)
-
-// string values for each IntegrationEmailFilterMode value
-const (
-	efmAll = "all-email"       // EmailFilterModeAll
-	efmOr  = "or-rules-email"  // EmailFilterModeOr
-	efmAnd = "and-rules-email" // EmailFilterModeAnd
-)
-
-func (i IntegrationEmailFilterMode) String() string {
-	switch i {
-	case EmailFilterModeAll:
-		return efmAll
-
-	case EmailFilterModeOr:
-		return efmOr
-
-	case EmailFilterModeAnd:
-		return efmAnd
-
-	default:
-		return "invalid"
-	}
-}
-
-// compile time encoding/json interface satisfaction assertions
-var (
-	_ json.Marshaler   = IntegrationEmailFilterMode(0)
-	_ json.Unmarshaler = (*IntegrationEmailFilterMode)(nil)
-)
-
-// MarshalJSON satisfies json.Marshaler
-func (i IntegrationEmailFilterMode) MarshalJSON() ([]byte, error) {
-	return []byte(fmt.Sprintf("%q", i.String())), nil
-}
-
-// UnmarshalJSON satisfies json.Unmarshaler
-func (i *IntegrationEmailFilterMode) UnmarshalJSON(b []byte) error {
-	if b[0] != '"' {
-		if bytes.Equal(b, []byte(`null`)) {
-			return errors.New("value cannot be null")
-		}
-
-		// just return json.Unmarshal error
-		var s string
-
-		err := json.Unmarshal(b, &s)
-		if err == nil {
-			panic("this should not be possible...")
-		}
-
-		return err
-	}
-
-	v := string(b[1 : len(b)-1])
-
-	switch v {
-	case efmAll:
-		*i = EmailFilterModeAll
-
-	case efmOr:
-		*i = EmailFilterModeOr
-
-	case efmAnd:
-		*i = EmailFilterModeAnd
-
-	default:
-		return fmt.Errorf("unknown value %q", v)
-	}
-
-	return nil
-}
-
-// IntegrationEmailFilterRuleMode is a type to represent the different matching
-// modes of Generic Email Integration Filer Rules without consumers of this
-// package needing to be intimately familiar with the specifics of the REST API.
-type IntegrationEmailFilterRuleMode uint8
-
-const (
-	// EmailFilterRuleModeInvalid only exists to make it harder to use values of this
-	// type incorrectly. Please instead use one of EmailFilterRuleModeAlways,
-	// EmailFilterRuleModeMatch, or EmailFilterRuleModeNoMatch.
-	//
-	// This value should not get marshaled to JSON by the encoding/json package.
-	EmailFilterRuleModeInvalid IntegrationEmailFilterRuleMode = iota
-
-	// EmailFilterRuleModeAlways means that the specific value can be anything. Any
-	// associated regular expression will be ignored.
-	EmailFilterRuleModeAlways
-
-	// EmailFilterRuleModeMatch means that the associated regular expression must
-	// match the associated value.
-	EmailFilterRuleModeMatch
-
-	// EmailFilterRuleModeNoMatch means that the associated regular expression must NOT
-	// match the associated value.
-	EmailFilterRuleModeNoMatch
-)
-
-// string values for each IntegrationEmailFilterRuleMode value
-const (
-	efrmAlways  = "always"   // EmailFilterRuleModeAlways
-	efrmMatch   = "match"    // EmailFilterRuleModeMatch
-	efrmNoMatch = "no-match" // EmailFilterRuleModeNoMatch
-)
-
-func (i IntegrationEmailFilterRuleMode) String() string {
-	switch i {
-	case EmailFilterRuleModeMatch:
-		return efrmMatch
-
-	case EmailFilterRuleModeNoMatch:
-		return efrmNoMatch
-
-	case EmailFilterRuleModeAlways:
-		return efrmAlways
-
-	default:
-		return "invalid"
-	}
-}
-
-// compile time encoding/json interface satisfaction assertions
-var (
-	_ json.Marshaler   = IntegrationEmailFilterRuleMode(0)
-	_ json.Unmarshaler = (*IntegrationEmailFilterRuleMode)(nil)
-)
-
-// MarshalJSON satisfies json.Marshaler
-func (i IntegrationEmailFilterRuleMode) MarshalJSON() ([]byte, error) {
-	return []byte(fmt.Sprintf("%q", i.String())), nil
-}
-
-// UnmarshalJSON satisfies json.Unmarshaler
-func (i *IntegrationEmailFilterRuleMode) UnmarshalJSON(b []byte) error {
-	if b[0] != '"' {
-		if bytes.Equal(b, []byte(`null`)) {
-			return errors.New("value cannot be null")
-		}
-
-		// just return json.Unmarshal error
-		var s string
-
-		err := json.Unmarshal(b, &s)
-		if err == nil {
-			panic("this should not be possible...")
-		}
-
-		return err
-	}
-
-	v := string(b[1 : len(b)-1])
-
-	switch v {
-	case efrmMatch:
-		*i = EmailFilterRuleModeMatch
-
-	case efrmNoMatch:
-		*i = EmailFilterRuleModeNoMatch
-
-	case efrmAlways:
-		*i = EmailFilterRuleModeAlways
-
-	default:
-		return fmt.Errorf("unknown value %q", v)
-	}
-
-	return nil
-}
-
-// IntegrationEmailFilterRule represents a single email filter rule for an
-// integration of type generic_email_inbound_integration. Information about how
-// to configure email rules can be found here:
-// https://support.pagerduty.com/docs/email-management-filters-and-rules.
-type IntegrationEmailFilterRule struct {
-	// SubjectMode and SubjectRegex control the behaviors of how this filter
-	// matches the subject of an inbound email.
-	SubjectMode  IntegrationEmailFilterRuleMode `json:"subject_mode,omitempty"`
-	SubjectRegex *string                        `json:"subject_regex,omitempty"`
-
-	// BodyMode and BodyRegex control the behaviors of how this filter matches
-	// the body of an inbound email.
-	BodyMode  IntegrationEmailFilterRuleMode `json:"body_mode,omitempty"`
-	BodyRegex *string                        `json:"body_regex,omitempty"`
-
-	FromEmailMode  IntegrationEmailFilterRuleMode `json:"from_email_mode,omitempty"`
-	FromEmailRegex *string                        `json:"from_email_regex,omitempty"`
-}
-
-// UnmarshalJSON satisfies json.Unmarshaler.
-func (i *IntegrationEmailFilterRule) UnmarshalJSON(b []byte) error {
-	// the purpose of this function is to ensure that when unmarshaling, the
-	// different *string values are never nil pointers.
-	//
-	// this is not a communicated feature of the API, so if it chnages
-	// it's not a breaking change -- doesn't mean we can't try.
-	var ief integrationEmailFilterRule
-	if err := json.Unmarshal(b, &ief); err != nil {
-		return err
-	}
-
-	i.BodyMode = ief.BodyMode
-	i.SubjectMode = ief.SubjectMode
-	i.FromEmailMode = ief.FromEmailMode
-
-	// if the *string is nil, set it to a *string with value ""
-	if ief.SubjectRegex == nil {
-		i.SubjectRegex = strPtr("")
-	} else {
-		i.SubjectRegex = ief.SubjectRegex
-	}
-
-	if ief.BodyRegex == nil {
-		i.BodyRegex = strPtr("")
-	} else {
-		i.BodyRegex = ief.BodyRegex
-	}
-
-	if ief.FromEmailRegex == nil {
-		i.FromEmailRegex = strPtr("")
-	} else {
-		i.FromEmailRegex = ief.FromEmailRegex
-	}
-
-	return nil
-}
-
-func strPtr(s string) *string { return &s }
-
-type integrationEmailFilterRule struct {
-	SubjectMode    IntegrationEmailFilterRuleMode `json:"subject_mode"`
-	SubjectRegex   *string                        `json:"subject_regex,omitempty"`
-	BodyMode       IntegrationEmailFilterRuleMode `json:"body_mode"`
-	BodyRegex      *string                        `json:"body_regex,omitempty"`
-	FromEmailMode  IntegrationEmailFilterRuleMode `json:"from_email_mode"`
-	FromEmailRegex *string                        `json:"from_email_regex,omitempty"`
-}
-
-// Integration is an endpoint (like Nagios, email, or an API call) that
-// generates events, which are normalized and de-duplicated by PagerDuty to
-// create incidents.
-type Integration struct {
-	APIObject
-	Name             string                       `json:"name,omitempty"`
-	Service          *APIObject                   `json:"service,omitempty"`
-	CreatedAt        string                       `json:"created_at,omitempty"`
-	Vendor           *APIObject                   `json:"vendor,omitempty"`
-	IntegrationKey   string                       `json:"integration_key,omitempty"`
-	IntegrationEmail string                       `json:"integration_email,omitempty"`
-	EmailFilterMode  IntegrationEmailFilterMode   `json:"email_filter_mode,omitempty"`
-	EmailFilters     []IntegrationEmailFilterRule `json:"email_filters,omitempty"`
-}
-
-// CreateIntegration creates a new integration belonging to a service.
-//
-// Deprecated: Use CreateIntegrationWithContext instead.
-func (c *Client) CreateIntegration(id string, i Integration) (*Integration, error) {
-	return c.CreateIntegrationWithContext(context.Background(), id, i)
-}
-
-// CreateIntegrationWithContext creates a new integration belonging to a service.
-func (c *Client) CreateIntegrationWithContext(ctx context.Context, id string, i Integration) (*Integration, error) {
-	d := map[string]Integration{
-		"integration": i,
-	}
-
-	resp, err := c.post(ctx, "/services/"+id+"/integrations", d, nil)
-	return getIntegrationFromResponse(c, resp, err)
-}
-
-// GetIntegrationOptions is the data structure used when calling the GetIntegration API endpoint.
-type GetIntegrationOptions struct {
-	Includes []string `url:"include,omitempty,brackets"`
-}
-
-// GetIntegration gets details about an integration belonging to a service.
-//
-// Deprecated: Use GetIntegrationWithContext instead.
-func (c *Client) GetIntegration(serviceID, integrationID string, o GetIntegrationOptions) (*Integration, error) {
-	return c.GetIntegrationWithContext(context.Background(), serviceID, integrationID, o)
-}
-
-// GetIntegrationWithContext gets details about an integration belonging to a service.
-func (c *Client) GetIntegrationWithContext(ctx context.Context, serviceID, integrationID string, o GetIntegrationOptions) (*Integration, error) {
-	v, err := query.Values(o)
-	if err != nil {
-		return nil, err
-	}
-
-	resp, err := c.get(ctx, "/services/"+serviceID+"/integrations/"+integrationID+"?"+v.Encode())
-	return getIntegrationFromResponse(c, resp, err)
-}
-
-// UpdateIntegration updates an integration belonging to a service.
-//
-// Deprecated: Use UpdateIntegrationWithContext instead.
-func (c *Client) UpdateIntegration(serviceID string, i Integration) (*Integration, error) {
-	return c.UpdateIntegrationWithContext(context.Background(), serviceID, i)
-}
-
-// UpdateIntegrationWithContext updates an integration belonging to a service.
-func (c *Client) UpdateIntegrationWithContext(ctx context.Context, serviceID string, i Integration) (*Integration, error) {
-	resp, err := c.put(ctx, "/services/"+serviceID+"/integrations/"+i.ID, i, nil)
-	return getIntegrationFromResponse(c, resp, err)
-}
-
-// DeleteIntegration deletes an existing integration.
-//
-// Deprecated: Use DeleteIntegrationWithContext instead.
-func (c *Client) DeleteIntegration(serviceID string, integrationID string) error {
-	return c.DeleteIntegrationWithContext(context.Background(), serviceID, integrationID)
-}
-
-// DeleteIntegrationWithContext deletes an existing integration.
-func (c *Client) DeleteIntegrationWithContext(ctx context.Context, serviceID string, integrationID string) error {
-	_, err := c.delete(ctx, "/services/"+serviceID+"/integrations/"+integrationID)
-	return err
-}
diff --git a/user.go b/user.go
index 22c610d..a4ae046 100644
--- a/user.go
+++ b/user.go
@@ -30,10 +30,10 @@ type User struct {
 	AvatarURL         string             `json:"avatar_url,omitempty"`
 	Description       string             `json:"description,omitempty"`
 	InvitationSent    bool               `json:"invitation_sent,omitempty"`
-	ContactMethods    []ContactMethod    `json:"contact_methods,omitempty"`
-	NotificationRules []NotificationRule `json:"notification_rules,omitempty"`
+	ContactMethods    []ContactMethod    `json:"contact_methods"`
+	NotificationRules []NotificationRule `json:"notification_rules"`
 	JobTitle          string             `json:"job_title,omitempty"`
-	Teams             []Team             `json:"teams,omitempty"`
+	Teams             []Team
 }
 
 // ContactMethod is a way of contacting the user.


Test patch:
diff --git a/change_events_test.go b/change_events_test.go
index 8a9c147..f197f6c 100644
--- a/change_events_test.go
+++ b/change_events_test.go
@@ -7,8 +7,8 @@ import (
 )
 
 const (
-	expectedChangeCreatePayload = `{"routing_key":"a0000000aa0000a0a000aa0a0a0aa000","payload":{"summary":"Summary can't be blank",` +
-		`"source":"Test runner","timestamp":"2020-10-19T03:06:16.318Z",` +
+	expectedChangeCreatePayload = `{"routing_key":"a0000000aa0000a0a000aa0a0a0aa000","payload":{"source":"Test runner",` +
+		`"summary":"Summary can't be blank","timestamp":"2020-10-19T03:06:16.318Z",` +
 		`"custom_details":{"DetailKey1":"DetailValue1","DetailKey2":"DetailValue2"}},` +
 		`"links":[{"href":"https://acme.pagerduty.dev/build/2","text":"View more details in Acme!"},` +
 		`{"href":"https://acme2.pagerduty.dev/build/2","text":"View more details in Acme2!"}]}`
diff --git a/client_test.go b/client_test.go
index 6ba0b30..f88cdb2 100644
--- a/client_test.go
+++ b/client_test.go
@@ -458,7 +458,7 @@ func TestClient_LastAPIRequest(t *testing.T) {
 	})
 
 	t.Run("integration", func(t *testing.T) {
-		const requestBody = `{"user":{"id":"1","name":"","summary":"","email":"foo@bar.com"}}`
+		const requestBody = `{"user":{"id":"1","name":"","summary":"","email":"foo@bar.com","contact_methods":null,"notification_rules":null,"Teams":null}}`
 
 		setup()
 		defer teardown()
@@ -680,65 +680,3 @@ func TestClient_Do(t *testing.T) {
 		})
 	}
 }
-
-func TestNullAPIErrorObject_UnmarshalJSON(t *testing.T) {
-	tests := []struct {
-		name string
-		json string
-		err  string
-		want *NullAPIErrorObject
-	}{
-		{
-			name: "error_per_api_spec",
-			json: `{"code":42,"message":"test message","errors":["first error","second error"]}`,
-			want: &NullAPIErrorObject{
-				Valid: true,
-				ErrorObject: APIErrorObject{
-					Code:    42,
-					Message: "test message",
-					Errors: []string{
-						"first error",
-						"second error",
-					},
-				},
-			},
-		},
-		{
-			name: "issue_339",
-			json: `{"code":84,"message":"other message","errors":"only error"}`,
-			want: &NullAPIErrorObject{
-				Valid: true,
-				ErrorObject: APIErrorObject{
-					Code:    84,
-					Message: "other message",
-					Errors: []string{
-						"only error",
-					},
-				},
-			},
-		},
-		{
-			name: "returns_type_errors",
-			json: `{"code":"42","message":"test message","errors":"first error"}`,
-			err:  "json: cannot unmarshal string into Go struct field APIErrorObject.code of type int",
-		},
-		{
-			name: "returns_syntax_errors",
-			json: `}`,
-			err:  "invalid character '}' looking for beginning of value",
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			got := &NullAPIErrorObject{}
-
-			err := json.Unmarshal([]byte(tt.json), &got)
-			if !testErrCheck(t, "*NullAPIErrorObject.UnmarshalJSON()", tt.err, err) {
-				return
-			}
-
-			testEqual(t, tt.want, got)
-		})
-	}
-}
diff --git a/incident_test.go b/incident_test.go
index cbab69d..5f70729 100644
--- a/incident_test.go
+++ b/incident_test.go
@@ -3,7 +3,6 @@ package pagerduty
 import (
 	"context"
 	"encoding/json"
-	"fmt"
 	"io/ioutil"
 	"net/http"
 	"testing"
@@ -46,38 +45,13 @@ func TestIncident_Create(t *testing.T) {
 	defer teardown()
 
 	input := &CreateIncidentOptions{
+		Type:    "incident",
 		Title:   "foo",
 		Urgency: "low",
 	}
 
 	mux.HandleFunc("/incidents", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, "POST")
-
-		body, err := ioutil.ReadAll(r.Body)
-		testErrCheck(t, "ioutil.ReadAll", "", err)
-
-		fmt.Println(string(body))
-
-		got := make(map[string]CreateIncidentOptions)
-		testErrCheck(t, "json.Unmarshal()", "", json.Unmarshal(body, &got))
-
-		o, ok := got["incident"]
-		if !ok {
-			t.Fatal("map does not have an incident key")
-		}
-
-		if o.Type != "incident" {
-			t.Errorf("o.Type = %q, want %q", o.Type, "incident")
-		}
-
-		if o.Title != "foo" {
-			t.Errorf("o.Foo = %q, want %q", o.Title, "foo")
-		}
-
-		if o.Urgency != "low" {
-			t.Errorf("o.Urgency = %q, want %q", o.Urgency, "low")
-		}
-
 		_, _ = w.Write([]byte(`{"incident": {"title": "foo", "id": "1", "urgency": "low"}}`))
 	})
 	client := defaultTestClient(server.URL, "foo")
@@ -102,56 +76,18 @@ func TestIncident_Manage_status(t *testing.T) {
 	setup()
 	defer teardown()
 
-	wantFrom := "foo@bar.com"
-
 	mux.HandleFunc("/incidents", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, "PUT")
-
-		if gotFrom := r.Header.Get("From"); gotFrom != wantFrom {
-			t.Errorf("From HTTP header = %q, want %q", gotFrom, wantFrom)
-		}
-
-		body, err := ioutil.ReadAll(r.Body)
-		if err != nil {
-			t.Fatal(err)
-		}
-
-		var data map[string][]ManageIncidentsOptions
-		testErrCheck(t, "json.Unmarshal()", "", json.Unmarshal(body, &data))
-
-		if len(data["incidents"]) == 0 {
-			t.Fatalf("no incidents, expect 1")
-		}
-
-		const (
-			wantType   = "incident"
-			wantID     = "1"
-			wantStatus = "acknowledged"
-		)
-
-		inc := data["incidents"][0]
-
-		if inc.Type != wantType {
-			t.Errorf("inc.Type = %q, want %q", inc.Type, wantType)
-		}
-
-		if inc.ID != wantID {
-			t.Errorf("inc.ID = %q, want %q", inc.ID, wantID)
-		}
-
-		if inc.Status != wantStatus {
-			t.Errorf("inc.Status = %q, want %q", inc.Status, wantStatus)
-		}
-
 		_, _ = w.Write([]byte(`{"incidents": [{"title": "foo", "id": "1", "status": "acknowledged"}]}`))
 	})
-
 	listObj := APIListObject{Limit: 0, Offset: 0, More: false, Total: 0}
 	client := defaultTestClient(server.URL, "foo")
+	from := "foo@bar.com"
 
 	input := []ManageIncidentsOptions{
 		{
 			ID:     "1",
+			Type:   "incident",
 			Status: "acknowledged",
 		},
 	}
@@ -168,7 +104,7 @@ func TestIncident_Manage_status(t *testing.T) {
 			},
 		},
 	}
-	res, err := client.ManageIncidents(wantFrom, input)
+	res, err := client.ManageIncidents(from, input)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -341,128 +277,6 @@ func TestIncident_Manage_assignments(t *testing.T) {
 	testEqual(t, want, res)
 }
 
-func TestIncident_Manage_conference_bridge(t *testing.T) {
-	setup()
-	defer teardown()
-
-	wantFrom := "foo@bar.com"
-
-	mux.HandleFunc("/incidents", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "PUT")
-
-		if gotFrom := r.Header.Get("From"); gotFrom != wantFrom {
-			t.Errorf("From HTTP header = %q, want %q", gotFrom, wantFrom)
-		}
-
-		body, err := ioutil.ReadAll(r.Body)
-		if err != nil {
-			t.Fatal(err)
-		}
-
-		var data map[string][]ManageIncidentsOptions
-		testErrCheck(t, "json.Unmarshal()", "", json.Unmarshal(body, &data))
-
-		if len(data["incidents"]) == 0 {
-			t.Fatalf("no incidents, expect 1")
-		}
-
-		const (
-			wantNum = "42"
-			wantURL = "http://example.org/bridge"
-		)
-
-		inc := data["incidents"][0]
-
-		if inc.ConferenceBridge == nil {
-			t.Fatalf("inc.ConferenceBridge = <nil>")
-		}
-
-		if got := inc.ConferenceBridge.ConferenceNumber; got != wantNum {
-			t.Fatalf("inc.ConferenceBridge.ConferenceNumber = %q, want %q", got, wantNum)
-		}
-
-		if got := inc.ConferenceBridge.ConferenceURL; got != wantURL {
-			t.Fatalf("inc.ConferenceBridge.ConferenceNumber = %q, want %q", got, wantURL)
-		}
-
-		_, _ = w.Write([]byte(`{"incidents": [{"title": "foo", "id": "1","conference_bridge":{"conference_number":"42","conference_url":"http://example.org/bridge"}}]}`))
-	})
-
-	client := defaultTestClient(server.URL, "foo")
-
-	input := []ManageIncidentsOptions{
-		{
-			ID:   "1",
-			Type: "incident",
-			ConferenceBridge: &ConferenceBridge{
-				ConferenceNumber: "42",
-				ConferenceURL:    "http://example.org/bridge",
-			},
-		},
-	}
-
-	want := &ListIncidentsResponse{
-		APIListObject: APIListObject{Limit: 0, Offset: 0, More: false, Total: 0},
-		Incidents: []Incident{
-			{
-				APIObject: APIObject{
-					ID: "1",
-				},
-				Title: "foo",
-				ConferenceBridge: &ConferenceBridge{
-					ConferenceNumber: "42",
-					ConferenceURL:    "http://example.org/bridge",
-				},
-			},
-		},
-	}
-
-	got, err := client.ManageIncidents(wantFrom, input)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	testEqual(t, want, got)
-}
-
-func TestIncident_Manage_esclation_level(t *testing.T) {
-	setup()
-	defer teardown()
-
-	mux.HandleFunc("/incidents", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "PUT")
-		_, _ = w.Write([]byte(`{"incidents": [{"title": "foo", "id": "1"}]}`))
-	})
-	listObj := APIListObject{Limit: 0, Offset: 0, More: false, Total: 0}
-	client := defaultTestClient(server.URL, "foo")
-	from := "foo@bar.com"
-
-	input := []ManageIncidentsOptions{
-		{
-			ID:              "1",
-			Type:            "incident",
-			EscalationLevel: 2,
-		},
-	}
-
-	want := &ListIncidentsResponse{
-		APIListObject: listObj,
-		Incidents: []Incident{
-			{
-				APIObject: APIObject{
-					ID: "1",
-				},
-				Title: "foo",
-			},
-		},
-	}
-	res, err := client.ManageIncidents(from, input)
-	if err != nil {
-		t.Fatal(err)
-	}
-	testEqual(t, want, res)
-}
-
 func TestIncident_Merge(t *testing.T) {
 	setup()
 	defer teardown()
@@ -895,17 +709,12 @@ func TestIncident_GetAlert(t *testing.T) {
 	testEqual(t, want, res)
 }
 
-func TestIncident_ManageIncidentAlerts(t *testing.T) {
+func TestIncident_ManageAlerts(t *testing.T) {
 	setup()
 	defer teardown()
 
-	from := "pagerduty@example.com"
-
 	mux.HandleFunc("/incidents/1/alerts/", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, "PUT")
-		if hdr := r.Header.Get("From"); hdr != "pagerduty@example.com" {
-			t.Errorf("From header = %q, want %q", hdr, from)
-		}
 		_, _ = w.Write([]byte(`{"alerts": [{"id": "1"}]}`))
 	})
 
@@ -922,7 +731,7 @@ func TestIncident_ManageIncidentAlerts(t *testing.T) {
 			},
 		},
 	}
-	res, err := client.ManageIncidentAlerts(context.Background(), incidentID, from, input)
+	res, err := client.ManageIncidentAlerts(incidentID, input)
 
 	want := &ListAlertsResponse{
 		Alerts: []IncidentAlert{
@@ -939,3 +748,55 @@ func TestIncident_ManageIncidentAlerts(t *testing.T) {
 	}
 	testEqual(t, want, res)
 }
+
+// CreateIncidentStatusUpdate
+func TestIncident_CreateIncidentStatusUpdate(t *testing.T) {
+	setup()
+	defer teardown()
+
+	wantId := "1"
+	wantFrom := "foo@bar.com"
+	wantMessage := "foo"
+
+	mux.HandleFunc("/incidents/1/status_updates", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "POST")
+
+		if gotFrom := r.Header.Get("From"); gotFrom != wantFrom {
+			t.Errorf("From HTTP header = %q, want %q", gotFrom, wantFrom)
+		}
+
+		body, err := ioutil.ReadAll(r.Body)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		var data map[string]string
+		if err := json.Unmarshal(body, &data); err != nil {
+			t.Fatal(err)
+		}
+		o, ok := data["message"]
+		if !ok {
+			t.Fatal("map does not have message key")
+		}
+		if o != wantMessage {
+			t.Errorf("message = %q, want %q", o, wantMessage)
+		}
+
+		_, _ = w.Write([]byte(`{"status_update": {"id": "1", "message": "foo", "sender": {"summary": "foo@bar.com", "type": "user_reference"}}}`))
+	})
+	client := defaultTestClient(server.URL, "foo")
+	res, err := client.CreateIncidentStatusUpdate(context.Background(), wantId, wantFrom, wantMessage)
+	want := IncidentStatusUpdate{
+		ID:      wantId,
+		Message: wantMessage,
+		Sender: APIObject{
+			Summary: wantFrom,
+			Type: "user_reference",
+		},
+	}
+
+	if err != nil {
+		t.Fatal(err)
+	}
+	testEqual(t, want, res)
+}
diff --git a/service_integration_test.go b/service_integration_test.go
deleted file mode 100644
index 2d4bce1..0000000
--- a/service_integration_test.go
+++ /dev/null
@@ -1,355 +0,0 @@
-package pagerduty
-
-import (
-	"encoding/json"
-	"fmt"
-	"net/http"
-	"testing"
-)
-
-// Create Integration
-func TestClient_CreateIntegration(t *testing.T) {
-	setup()
-	defer teardown()
-
-	mux.HandleFunc("/services/1/integrations", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "POST")
-		_, _ = w.Write([]byte(`{"integration": {"id": "1","name":"foo"}}`))
-	})
-
-	client := defaultTestClient(server.URL, "foo")
-	input := Integration{
-		Name: "foo",
-	}
-	servID := "1"
-
-	res, err := client.CreateIntegration(servID, input)
-
-	want := &Integration{
-		APIObject: APIObject{
-			ID: "1",
-		},
-		Name: "foo",
-	}
-
-	if err != nil {
-		t.Fatal(err)
-	}
-	testEqual(t, want, res)
-}
-
-// Get Integration
-func TestClient_GetIntegration(t *testing.T) {
-	setup()
-	defer teardown()
-
-	mux.HandleFunc("/services/1/integrations/1", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "GET")
-		_, _ = w.Write([]byte(`{"integration": {"id": "1","name":"foo"}}`))
-	})
-
-	client := defaultTestClient(server.URL, "foo")
-	input := GetIntegrationOptions{
-		Includes: []string{},
-	}
-	servID := "1"
-	intID := "1"
-
-	res, err := client.GetIntegration(servID, intID, input)
-
-	want := &Integration{
-		APIObject: APIObject{
-			ID: "1",
-		},
-		Name: "foo",
-	}
-
-	if err != nil {
-		t.Fatal(err)
-	}
-	testEqual(t, want, res)
-}
-
-// Update Integration
-func TestClient_UpdateIntegration(t *testing.T) {
-	setup()
-	defer teardown()
-
-	mux.HandleFunc("/services/1/integrations/1", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "PUT")
-		_, _ = w.Write([]byte(`{"integration": {"id": "1","name":"foo"}}`))
-	})
-
-	client := defaultTestClient(server.URL, "foo")
-	input := Integration{
-		APIObject: APIObject{
-			ID: "1",
-		},
-		Name: "foo",
-	}
-	servID := "1"
-
-	res, err := client.UpdateIntegration(servID, input)
-
-	want := &Integration{
-		APIObject: APIObject{
-			ID: "1",
-		},
-		Name: "foo",
-	}
-
-	if err != nil {
-		t.Fatal(err)
-	}
-	testEqual(t, want, res)
-}
-
-// Delete Integration
-func TestClient_DeleteIntegration(t *testing.T) {
-	setup()
-	defer teardown()
-
-	mux.HandleFunc("/services/1/integrations/1", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "DELETE")
-	})
-
-	client := defaultTestClient(server.URL, "foo")
-	servID := "1"
-	intID := "1"
-	err := client.DeleteIntegration(servID, intID)
-	if err != nil {
-		t.Fatal(err)
-	}
-}
-
-func TestIntegrationEmailFilterMode_String(t *testing.T) {
-	tests := []struct {
-		name  string
-		input IntegrationEmailFilterMode
-		want  string
-	}{
-		{name: "unknown", input: ^IntegrationEmailFilterMode(0), want: "invalid"},
-		{name: "invalid", input: EmailFilterModeInvalid, want: "invalid"},
-		{name: "all", input: EmailFilterModeAll, want: "all-email"},
-		{name: "or", input: EmailFilterModeOr, want: "or-rules-email"},
-		{name: "and", input: EmailFilterModeAnd, want: "and-rules-email"},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			if got := tt.input.String(); got != tt.want {
-				t.Fatalf("String() = %q, want %q", got, tt.want)
-			}
-		})
-	}
-}
-
-func TestIntegrationEmailFilterMode_MarshalJSON(t *testing.T) {
-	tests := []struct {
-		name  string
-		input IntegrationEmailFilterMode
-		want  string
-	}{
-		{name: "unknown", input: ^IntegrationEmailFilterMode(0), want: "invalid"},
-		{name: "invalid", input: EmailFilterModeInvalid, want: "invalid"},
-		{name: "all", input: EmailFilterModeAll, want: "all-email"},
-		{name: "or", input: EmailFilterModeOr, want: "or-rules-email"},
-		{name: "and", input: EmailFilterModeAnd, want: "and-rules-email"},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			json, err := tt.input.MarshalJSON()
-			testErrCheck(t, "tt.input.MarshalJSON()", "", err)
-
-			want := fmt.Sprintf("%q", tt.want)
-
-			if got := string(json); got != want {
-				t.Fatalf("MarshalJSON() = `%s`, want `%s`", got, want)
-			}
-		})
-	}
-}
-
-func TestIntegrationEmailFilterMode_UnmarshalJSON(t *testing.T) {
-	tests := []struct {
-		name  string
-		input string
-		want  IntegrationEmailFilterMode
-		err   string
-	}{
-		{name: "invalid", input: `"invalid"`, err: `unknown value "invalid"`},
-		{name: "null", input: `null`, err: `value cannot be null`},
-		{name: "not_string", input: `42`, err: `json: cannot unmarshal number into Go value of type string`},
-		{name: "all", input: `"all-email"`, want: EmailFilterModeAll},
-		{name: "or", input: `"or-rules-email"`, want: EmailFilterModeOr},
-		{name: "and", input: `"and-rules-email"`, want: EmailFilterModeAnd},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			got := new(IntegrationEmailFilterMode)
-
-			if cont := testErrCheck(t, "got.UnmarshalJSON()", tt.err, got.UnmarshalJSON([]byte(tt.input))); !cont {
-				return
-			}
-
-			if *got != tt.want {
-				t.Fatalf("got = %d (%s), want = %d (%s)", *got, got.String(), tt.want, tt.want.String())
-			}
-		})
-	}
-}
-
-func TestIntegrationEmailFilterRuleMode_String(t *testing.T) {
-	tests := []struct {
-		name  string
-		input IntegrationEmailFilterRuleMode
-		want  string
-	}{
-		{name: "unknown", input: ^IntegrationEmailFilterRuleMode(0), want: "invalid"},
-		{name: "invalid", input: EmailFilterRuleModeInvalid, want: "invalid"},
-		{name: "always", input: EmailFilterRuleModeAlways, want: "always"},
-		{name: "match", input: EmailFilterRuleModeMatch, want: "match"},
-		{name: "no_match", input: EmailFilterRuleModeNoMatch, want: "no-match"},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			if got := tt.input.String(); got != tt.want {
-				t.Fatalf("String() = %q, want %q", got, tt.want)
-			}
-		})
-	}
-}
-
-func TestIntegrationEmailFilterRuleMode_MarshalJSON(t *testing.T) {
-	tests := []struct {
-		name  string
-		input IntegrationEmailFilterRuleMode
-		want  string
-	}{
-		{name: "unknown", input: ^IntegrationEmailFilterRuleMode(0), want: "invalid"},
-		{name: "invalid", input: EmailFilterRuleModeInvalid, want: "invalid"},
-		{name: "always", input: EmailFilterRuleModeAlways, want: "always"},
-		{name: "match", input: EmailFilterRuleModeMatch, want: "match"},
-		{name: "no_match", input: EmailFilterRuleModeNoMatch, want: "no-match"},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			json, err := tt.input.MarshalJSON()
-			testErrCheck(t, "tt.input.MarshalJSON()", "", err)
-
-			want := fmt.Sprintf("%q", tt.want)
-
-			if got := string(json); got != want {
-				t.Fatalf("MarshalJSON() = `%s`, want `%s`", got, want)
-			}
-		})
-	}
-}
-
-func TestIntegrationEmailFilterRuleMode_UnmarshalJSON(t *testing.T) {
-	tests := []struct {
-		name  string
-		input string
-		want  IntegrationEmailFilterRuleMode
-		err   string
-	}{
-		{name: "invalid", input: `"invalid"`, err: `unknown value "invalid"`},
-		{name: "null", input: `null`, err: `value cannot be null`},
-		{name: "not_string", input: `42`, err: `json: cannot unmarshal number into Go value of type string`},
-		{name: "always", input: `"always"`, want: EmailFilterRuleModeAlways},
-		{name: "match", input: `"match"`, want: EmailFilterRuleModeMatch},
-		{name: "no_match", input: `"no-match"`, want: EmailFilterRuleModeNoMatch},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			got := new(IntegrationEmailFilterRuleMode)
-
-			if cont := testErrCheck(t, "got.UnmarshalJSON()", tt.err, got.UnmarshalJSON([]byte(tt.input))); !cont {
-				return
-			}
-
-			if *got != tt.want {
-				t.Fatalf("got = %d (%s), want = %d (%s)", *got, got.String(), tt.want, tt.want.String())
-			}
-		})
-	}
-}
-
-func TestIntegrationEmailFilterRule(t *testing.T) {
-	t.Run("zero_value/omitempty", func(t *testing.T) {
-		var iefr IntegrationEmailFilterRule
-
-		j, err := json.Marshal(iefr)
-		testErrCheck(t, "json.Marshal()", "", err)
-
-		if string(j) != "{}" {
-			t.Fatalf("expected empty object, got %q", string(j))
-		}
-	})
-}
-
-func TestIntegrationEmailFilterRule_UnmarshalJSON(t *testing.T) {
-	tests := []struct {
-		name  string
-		input string
-		want  IntegrationEmailFilterRule
-		err   string
-	}{
-		{
-			name:  "full",
-			input: `{"subject_mode":"always", "subject_regex":"", "body_mode":"match", "body_regex":"testbody", "from_email_mode":"no-match", "from_email_regex":"testfrom"}`,
-			want: IntegrationEmailFilterRule{
-				SubjectMode:    EmailFilterRuleModeAlways,
-				SubjectRegex:   strPtr(""),
-				BodyMode:       EmailFilterRuleModeMatch,
-				BodyRegex:      strPtr("testbody"),
-				FromEmailMode:  EmailFilterRuleModeNoMatch,
-				FromEmailRegex: strPtr("testfrom"),
-			},
-		},
-
-		{
-			name:  "empty",
-			input: `{}`,
-			want: IntegrationEmailFilterRule{
-				SubjectMode:    EmailFilterRuleModeInvalid,
-				SubjectRegex:   strPtr(""),
-				BodyMode:       EmailFilterRuleModeInvalid,
-				BodyRegex:      strPtr(""),
-				FromEmailMode:  EmailFilterRuleModeInvalid,
-				FromEmailRegex: strPtr(""),
-			},
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			got := new(IntegrationEmailFilterRule)
-
-			err := got.UnmarshalJSON([]byte(tt.input))
-
-			if !testErrCheck(t, "got.UnmarshalJSON()", tt.err, err) {
-				return
-			}
-
-			if got.SubjectRegex == nil {
-				t.Error("got.SubjectRegex = <nil>")
-			}
-
-			if got.BodyRegex == nil {
-				t.Error("got.BodyRegex = <nil>")
-			}
-
-			if got.FromEmailRegex == nil {
-				t.Error("got.FromEmailRegex = <nil>")
-			}
-
-			testEqual(t, tt.want, *got)
-		})
-	}
-}
diff --git a/service_test.go b/service_test.go
index dbcc530..6f05a0f 100644
--- a/service_test.go
+++ b/service_test.go
@@ -314,6 +314,121 @@ func TestService_Delete(t *testing.T) {
 	}
 }
 
+// Create Integration
+func TestService_CreateIntegration(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc("/services/1/integrations", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "POST")
+		_, _ = w.Write([]byte(`{"integration": {"id": "1","name":"foo"}}`))
+	})
+
+	client := defaultTestClient(server.URL, "foo")
+	input := Integration{
+		Name: "foo",
+	}
+	servID := "1"
+
+	res, err := client.CreateIntegration(servID, input)
+
+	want := &Integration{
+		APIObject: APIObject{
+			ID: "1",
+		},
+		Name: "foo",
+	}
+
+	if err != nil {
+		t.Fatal(err)
+	}
+	testEqual(t, want, res)
+}
+
+// Get Integration
+func TestService_GetIntegration(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc("/services/1/integrations/1", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		_, _ = w.Write([]byte(`{"integration": {"id": "1","name":"foo"}}`))
+	})
+
+	client := defaultTestClient(server.URL, "foo")
+	input := GetIntegrationOptions{
+		Includes: []string{},
+	}
+	servID := "1"
+	intID := "1"
+
+	res, err := client.GetIntegration(servID, intID, input)
+
+	want := &Integration{
+		APIObject: APIObject{
+			ID: "1",
+		},
+		Name: "foo",
+	}
+
+	if err != nil {
+		t.Fatal(err)
+	}
+	testEqual(t, want, res)
+}
+
+// Update Integration
+func TestService_UpdateIntegration(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc("/services/1/integrations/1", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "PUT")
+		_, _ = w.Write([]byte(`{"integration": {"id": "1","name":"foo"}}`))
+	})
+
+	client := defaultTestClient(server.URL, "foo")
+	input := Integration{
+		APIObject: APIObject{
+			ID: "1",
+		},
+		Name: "foo",
+	}
+	servID := "1"
+
+	res, err := client.UpdateIntegration(servID, input)
+
+	want := &Integration{
+		APIObject: APIObject{
+			ID: "1",
+		},
+		Name: "foo",
+	}
+
+	if err != nil {
+		t.Fatal(err)
+	}
+	testEqual(t, want, res)
+}
+
+// Delete Integration
+func TestService_DeleteIntegration(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc("/services/1/integrations/1", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "DELETE")
+	})
+
+	client := defaultTestClient(server.URL, "foo")
+	servID := "1"
+	intID := "1"
+	err := client.DeleteIntegration(servID, intID)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
 // List Service Rules
 func TestService_ListRules(t *testing.T) {
 	setup()