pax_global_header 0000666 0000000 0000000 00000000064 14604223742 0014516 g ustar 00root root 0000000 0000000 52 comment=9dbb4c3be9314ca0985a3fd32f393368cd998957
kin-openapi-0.124.0/ 0000775 0000000 0000000 00000000000 14604223742 0014074 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/.gitattributes 0000664 0000000 0000000 00000000022 14604223742 0016761 0 ustar 00root root 0000000 0000000 *.yml text eol=lf
kin-openapi-0.124.0/.github/ 0000775 0000000 0000000 00000000000 14604223742 0015434 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/.github/FUNDING.yml 0000664 0000000 0000000 00000001332 14604223742 0017250 0 ustar 00root root 0000000 0000000 # These are supported funding model platforms
github: [fenollp] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
#patreon: # Replace with a single Patreon username
#open_collective: # Replace with a single Open Collective username
#ko_fi: # Replace with a single Ko-fi username
#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
#liberapay: # Replace with a single Liberapay username
#issuehunt: # Replace with a single IssueHunt username
#otechie: # Replace with a single Otechie username
#custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
kin-openapi-0.124.0/.github/docs/ 0000775 0000000 0000000 00000000000 14604223742 0016364 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/.github/docs/openapi2.txt 0000664 0000000 0000000 00000023005 14604223742 0020642 0 ustar 00root root 0000000 0000000 package openapi2 // import "github.com/getkin/kin-openapi/openapi2"
Package openapi2 parses and writes OpenAPIv2 specification documents.
Does not cover all elements of OpenAPIv2. When OpenAPI version 3 is
backwards-compatible with version 2, version 3 elements have been used.
See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md
TYPES
type Header struct {
Parameter
}
func (header Header) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Header.
func (header *Header) UnmarshalJSON(data []byte) error
UnmarshalJSON sets Header to a copy of data.
type Operation struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Summary string `json:"summary,omitempty" yaml:"summary,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
ExternalDocs *openapi3.ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"`
OperationID string `json:"operationId,omitempty" yaml:"operationId,omitempty"`
Parameters Parameters `json:"parameters,omitempty" yaml:"parameters,omitempty"`
Responses map[string]*Response `json:"responses" yaml:"responses"`
Consumes []string `json:"consumes,omitempty" yaml:"consumes,omitempty"`
Produces []string `json:"produces,omitempty" yaml:"produces,omitempty"`
Schemes []string `json:"schemes,omitempty" yaml:"schemes,omitempty"`
Security *SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"`
}
func (operation Operation) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Operation.
func (operation *Operation) UnmarshalJSON(data []byte) error
UnmarshalJSON sets Operation to a copy of data.
type Parameter struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
In string `json:"in,omitempty" yaml:"in,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
CollectionFormat string `json:"collectionFormat,omitempty" yaml:"collectionFormat,omitempty"`
Type *openapi3.Types `json:"type,omitempty" yaml:"type,omitempty"`
Format string `json:"format,omitempty" yaml:"format,omitempty"`
Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"`
AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"`
Required bool `json:"required,omitempty" yaml:"required,omitempty"`
UniqueItems bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"`
ExclusiveMin bool `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"`
ExclusiveMax bool `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"`
Schema *openapi3.SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"`
Items *openapi3.SchemaRef `json:"items,omitempty" yaml:"items,omitempty"`
Enum []interface{} `json:"enum,omitempty" yaml:"enum,omitempty"`
MultipleOf *float64 `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"`
Minimum *float64 `json:"minimum,omitempty" yaml:"minimum,omitempty"`
Maximum *float64 `json:"maximum,omitempty" yaml:"maximum,omitempty"`
MaxLength *uint64 `json:"maxLength,omitempty" yaml:"maxLength,omitempty"`
MaxItems *uint64 `json:"maxItems,omitempty" yaml:"maxItems,omitempty"`
MinLength uint64 `json:"minLength,omitempty" yaml:"minLength,omitempty"`
MinItems uint64 `json:"minItems,omitempty" yaml:"minItems,omitempty"`
Default interface{} `json:"default,omitempty" yaml:"default,omitempty"`
}
func (parameter Parameter) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Parameter.
func (parameter *Parameter) UnmarshalJSON(data []byte) error
UnmarshalJSON sets Parameter to a copy of data.
type Parameters []*Parameter
func (ps Parameters) Len() int
func (ps Parameters) Less(i, j int) bool
func (ps Parameters) Swap(i, j int)
type PathItem struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
Delete *Operation `json:"delete,omitempty" yaml:"delete,omitempty"`
Get *Operation `json:"get,omitempty" yaml:"get,omitempty"`
Head *Operation `json:"head,omitempty" yaml:"head,omitempty"`
Options *Operation `json:"options,omitempty" yaml:"options,omitempty"`
Patch *Operation `json:"patch,omitempty" yaml:"patch,omitempty"`
Post *Operation `json:"post,omitempty" yaml:"post,omitempty"`
Put *Operation `json:"put,omitempty" yaml:"put,omitempty"`
Parameters Parameters `json:"parameters,omitempty" yaml:"parameters,omitempty"`
}
func (pathItem *PathItem) GetOperation(method string) *Operation
func (pathItem PathItem) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of PathItem.
func (pathItem *PathItem) Operations() map[string]*Operation
func (pathItem *PathItem) SetOperation(method string, operation *Operation)
func (pathItem *PathItem) UnmarshalJSON(data []byte) error
UnmarshalJSON sets PathItem to a copy of data.
type Response struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Schema *openapi3.SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"`
Headers map[string]*Header `json:"headers,omitempty" yaml:"headers,omitempty"`
Examples map[string]interface{} `json:"examples,omitempty" yaml:"examples,omitempty"`
}
func (response Response) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Response.
func (response *Response) UnmarshalJSON(data []byte) error
UnmarshalJSON sets Response to a copy of data.
type SecurityRequirements []map[string][]string
type SecurityScheme struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Type string `json:"type,omitempty" yaml:"type,omitempty"`
In string `json:"in,omitempty" yaml:"in,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Flow string `json:"flow,omitempty" yaml:"flow,omitempty"`
AuthorizationURL string `json:"authorizationUrl,omitempty" yaml:"authorizationUrl,omitempty"`
TokenURL string `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"`
Scopes map[string]string `json:"scopes,omitempty" yaml:"scopes,omitempty"`
Tags openapi3.Tags `json:"tags,omitempty" yaml:"tags,omitempty"`
}
func (securityScheme SecurityScheme) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of SecurityScheme.
func (securityScheme *SecurityScheme) UnmarshalJSON(data []byte) error
UnmarshalJSON sets SecurityScheme to a copy of data.
type T struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Swagger string `json:"swagger" yaml:"swagger"` // required
Info openapi3.Info `json:"info" yaml:"info"` // required
ExternalDocs *openapi3.ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
Schemes []string `json:"schemes,omitempty" yaml:"schemes,omitempty"`
Consumes []string `json:"consumes,omitempty" yaml:"consumes,omitempty"`
Produces []string `json:"produces,omitempty" yaml:"produces,omitempty"`
Host string `json:"host,omitempty" yaml:"host,omitempty"`
BasePath string `json:"basePath,omitempty" yaml:"basePath,omitempty"`
Paths map[string]*PathItem `json:"paths,omitempty" yaml:"paths,omitempty"`
Definitions map[string]*openapi3.SchemaRef `json:"definitions,omitempty" yaml:"definitions,omitempty"`
Parameters map[string]*Parameter `json:"parameters,omitempty" yaml:"parameters,omitempty"`
Responses map[string]*Response `json:"responses,omitempty" yaml:"responses,omitempty"`
SecurityDefinitions map[string]*SecurityScheme `json:"securityDefinitions,omitempty" yaml:"securityDefinitions,omitempty"`
Security SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"`
Tags openapi3.Tags `json:"tags,omitempty" yaml:"tags,omitempty"`
}
T is the root of an OpenAPI v2 document
func (doc *T) AddOperation(path string, method string, operation *Operation)
func (doc T) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of T.
func (doc *T) UnmarshalJSON(data []byte) error
UnmarshalJSON sets T to a copy of data.
kin-openapi-0.124.0/.github/docs/openapi2conv.txt 0000664 0000000 0000000 00000005671 14604223742 0021541 0 ustar 00root root 0000000 0000000 package openapi2conv // import "github.com/getkin/kin-openapi/openapi2conv"
Package openapi2conv converts an OpenAPI v2 specification document to v3.
FUNCTIONS
func FromV3(doc3 *openapi3.T) (*openapi2.T, error)
FromV3 converts an OpenAPIv3 spec to an OpenAPIv2 spec
func FromV3Headers(defs openapi3.Headers, components *openapi3.Components) (map[string]*openapi2.Header, error)
func FromV3Operation(doc3 *openapi3.T, operation *openapi3.Operation) (*openapi2.Operation, error)
func FromV3Parameter(ref *openapi3.ParameterRef, components *openapi3.Components) (*openapi2.Parameter, error)
func FromV3PathItem(doc3 *openapi3.T, pathItem *openapi3.PathItem) (*openapi2.PathItem, error)
func FromV3Ref(ref string) string
func FromV3RequestBody(name string, requestBodyRef *openapi3.RequestBodyRef, mediaType *openapi3.MediaType, components *openapi3.Components) (*openapi2.Parameter, error)
func FromV3RequestBodyFormData(mediaType *openapi3.MediaType) openapi2.Parameters
func FromV3Response(ref *openapi3.ResponseRef, components *openapi3.Components) (*openapi2.Response, error)
func FromV3Responses(responses map[string]*openapi3.ResponseRef, components *openapi3.Components) (map[string]*openapi2.Response, error)
func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components) (*openapi3.SchemaRef, *openapi2.Parameter)
func FromV3Schemas(schemas map[string]*openapi3.SchemaRef, components *openapi3.Components) (map[string]*openapi3.SchemaRef, map[string]*openapi2.Parameter)
func FromV3SecurityRequirements(requirements openapi3.SecurityRequirements) openapi2.SecurityRequirements
func FromV3SecurityScheme(doc3 *openapi3.T, ref *openapi3.SecuritySchemeRef) (*openapi2.SecurityScheme, error)
func ToV3(doc2 *openapi2.T) (*openapi3.T, error)
ToV3 converts an OpenAPIv2 spec to an OpenAPIv3 spec
func ToV3Headers(defs map[string]*openapi2.Header) openapi3.Headers
func ToV3Operation(doc2 *openapi2.T, components *openapi3.Components, pathItem *openapi2.PathItem, operation *openapi2.Operation, consumes []string) (*openapi3.Operation, error)
func ToV3Parameter(components *openapi3.Components, parameter *openapi2.Parameter, consumes []string) (*openapi3.ParameterRef, *openapi3.RequestBodyRef, map[string]*openapi3.SchemaRef, error)
func ToV3PathItem(doc2 *openapi2.T, components *openapi3.Components, pathItem *openapi2.PathItem, consumes []string) (*openapi3.PathItem, error)
func ToV3Ref(ref string) string
func ToV3Response(response *openapi2.Response, produces []string) (*openapi3.ResponseRef, error)
func ToV3SchemaRef(schema *openapi3.SchemaRef) *openapi3.SchemaRef
func ToV3Schemas(defs map[string]*openapi3.SchemaRef) map[string]*openapi3.SchemaRef
func ToV3SecurityRequirements(requirements openapi2.SecurityRequirements) openapi3.SecurityRequirements
func ToV3SecurityScheme(securityScheme *openapi2.SecurityScheme) (*openapi3.SecuritySchemeRef, error)
func ToV3WithLoader(doc2 *openapi2.T, loader *openapi3.Loader, location *url.URL) (*openapi3.T, error)
kin-openapi-0.124.0/.github/docs/openapi3.txt 0000664 0000000 0000000 00000217444 14604223742 0020657 0 ustar 00root root 0000000 0000000 package openapi3 // import "github.com/getkin/kin-openapi/openapi3"
Package openapi3 parses and writes OpenAPI 3 specification documents.
See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md
CONSTANTS
const (
ParameterInPath = "path"
ParameterInQuery = "query"
ParameterInHeader = "header"
ParameterInCookie = "cookie"
)
const (
TypeArray = "array"
TypeBoolean = "boolean"
TypeInteger = "integer"
TypeNumber = "number"
TypeObject = "object"
TypeString = "string"
TypeNull = "null"
)
const (
// FormatOfStringForUUIDOfRFC4122 is an optional predefined format for UUID v1-v5 as specified by RFC4122
FormatOfStringForUUIDOfRFC4122 = `^(?:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000)$`
// FormatOfStringForEmail pattern catches only some suspiciously wrong-looking email addresses.
// Use DefineStringFormat(...) if you need something stricter.
FormatOfStringForEmail = `^[^@]+@[^@<>",\s]+$`
)
const (
SerializationSimple = "simple"
SerializationLabel = "label"
SerializationMatrix = "matrix"
SerializationForm = "form"
SerializationSpaceDelimited = "spaceDelimited"
SerializationPipeDelimited = "pipeDelimited"
SerializationDeepObject = "deepObject"
)
VARIABLES
var (
// SchemaErrorDetailsDisabled disables printing of details about schema errors.
SchemaErrorDetailsDisabled = false
// ErrOneOfConflict is the SchemaError Origin when data matches more than one oneOf schema
ErrOneOfConflict = errors.New("input matches more than one oneOf schemas")
// ErrSchemaInputNaN may be returned when validating a number
ErrSchemaInputNaN = errors.New("floating point NaN is not allowed")
// ErrSchemaInputInf may be returned when validating a number
ErrSchemaInputInf = errors.New("floating point Inf is not allowed")
)
var CircularReferenceCounter = 3
var CircularReferenceError = "kin-openapi bug found: circular schema reference not handled"
var DefaultReadFromURI = URIMapCache(ReadFromURIs(ReadFromHTTP(http.DefaultClient), ReadFromFile))
DefaultReadFromURI returns a caching ReadFromURIFunc which can read remote
HTTP URIs and local file URIs.
var ErrURINotSupported = errors.New("unsupported URI")
ErrURINotSupported indicates the ReadFromURIFunc does not know how to handle
a given URI.
var IdentifierRegExp = regexp.MustCompile(identifierPattern)
IdentifierRegExp verifies whether Component object key matches
'identifierPattern' pattern, according to OpenAPI v3.x. However, to be able
supporting legacy OpenAPI v2.x, there is a need to customize above pattern
in order not to fail converted v2-v3 validation
var SchemaStringFormats = make(map[string]Format, 4)
SchemaStringFormats allows for validating string formats
FUNCTIONS
func BoolPtr(value bool) *bool
BoolPtr is a helper for defining OpenAPI schemas.
func DefaultRefNameResolver(ref string) string
DefaultRefResolver is a default implementation of refNameResolver for the
InternalizeRefs function.
If a reference points to an element inside a document, it returns the last
element in the reference using filepath.Base. Otherwise if the reference
points to a file, it returns the file name trimmed of all extensions.
func DefineIPv4Format()
DefineIPv4Format opts in ipv4 format validation on top of OAS 3 spec
func DefineIPv6Format()
DefineIPv6Format opts in ipv6 format validation on top of OAS 3 spec
func DefineStringFormat(name string, pattern string)
DefineStringFormat defines a new regexp pattern for a given format
func DefineStringFormatCallback(name string, callback FormatCallback)
DefineStringFormatCallback adds a validation function for a specific schema
format entry
func Float64Ptr(value float64) *float64
Float64Ptr is a helper for defining OpenAPI schemas.
func Int64Ptr(value int64) *int64
Int64Ptr is a helper for defining OpenAPI schemas.
func ReadFromFile(loader *Loader, location *url.URL) ([]byte, error)
ReadFromFile is a ReadFromURIFunc which reads local file URIs.
func RegisterArrayUniqueItemsChecker(fn SliceUniqueItemsChecker)
RegisterArrayUniqueItemsChecker is used to register a customized function
used to check if JSON array have unique items.
func Uint64Ptr(value uint64) *uint64
Uint64Ptr is a helper for defining OpenAPI schemas.
func ValidateIdentifier(value string) error
ValidateIdentifier returns an error if the given component name does not
match IdentifierRegExp.
func WithValidationOptions(ctx context.Context, opts ...ValidationOption) context.Context
WithValidationOptions allows adding validation options to a context object
that can be used when validating any OpenAPI type.
TYPES
type AdditionalProperties struct {
Has *bool
Schema *SchemaRef
}
func (addProps AdditionalProperties) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of AdditionalProperties.
func (addProps AdditionalProperties) MarshalYAML() (interface{}, error)
MarshalYAML returns the YAML encoding of AdditionalProperties.
func (addProps *AdditionalProperties) UnmarshalJSON(data []byte) error
UnmarshalJSON sets AdditionalProperties to a copy of data.
type Callback struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
// Has unexported fields.
}
Callback is specified by OpenAPI/Swagger standard version 3. See
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#callback-object
func NewCallback(opts ...NewCallbackOption) *Callback
NewCallback builds a Callback object with path items in insertion order.
func NewCallbackWithCapacity(cap int) *Callback
NewCallbackWithCapacity builds a callback object of the given capacity.
func (callback *Callback) Delete(key string)
Delete removes the entry associated with key 'key' from 'callback'.
func (callback Callback) JSONLookup(token string) (interface{}, error)
JSONLookup implements
https://github.com/go-openapi/jsonpointer#JSONPointable
func (callback *Callback) Len() int
Len returns the amount of keys in callback excluding callback.Extensions.
func (callback *Callback) Map() (m map[string]*PathItem)
Map returns callback as a 'map'. Note: iteration on Go maps is not ordered.
func (callback *Callback) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Callback.
func (callback *Callback) Set(key string, value *PathItem)
Set adds or replaces key 'key' of 'callback' with 'value'. Note: 'callback'
MUST be non-nil
func (callback *Callback) UnmarshalJSON(data []byte) (err error)
UnmarshalJSON sets Callback to a copy of data.
func (callback *Callback) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if Callback does not comply with the OpenAPI spec.
func (callback *Callback) Value(key string) *PathItem
Value returns the callback for key or nil
type CallbackRef struct {
Ref string
Value *Callback
// Has unexported fields.
}
CallbackRef represents either a Callback or a $ref to a Callback. When
serializing and both fields are set, Ref is preferred over Value.
func (x *CallbackRef) JSONLookup(token string) (interface{}, error)
JSONLookup implements
https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (x CallbackRef) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of CallbackRef.
func (x CallbackRef) MarshalYAML() (interface{}, error)
MarshalYAML returns the YAML encoding of CallbackRef.
func (x *CallbackRef) UnmarshalJSON(data []byte) error
UnmarshalJSON sets CallbackRef to a copy of data.
func (x *CallbackRef) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if CallbackRef does not comply with the OpenAPI
spec.
type Callbacks map[string]*CallbackRef
func (m Callbacks) JSONLookup(token string) (interface{}, error)
JSONLookup implements
https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
type Components struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Schemas Schemas `json:"schemas,omitempty" yaml:"schemas,omitempty"`
Parameters ParametersMap `json:"parameters,omitempty" yaml:"parameters,omitempty"`
Headers Headers `json:"headers,omitempty" yaml:"headers,omitempty"`
RequestBodies RequestBodies `json:"requestBodies,omitempty" yaml:"requestBodies,omitempty"`
Responses ResponseBodies `json:"responses,omitempty" yaml:"responses,omitempty"`
SecuritySchemes SecuritySchemes `json:"securitySchemes,omitempty" yaml:"securitySchemes,omitempty"`
Examples Examples `json:"examples,omitempty" yaml:"examples,omitempty"`
Links Links `json:"links,omitempty" yaml:"links,omitempty"`
Callbacks Callbacks `json:"callbacks,omitempty" yaml:"callbacks,omitempty"`
}
Components is specified by OpenAPI/Swagger standard version 3. See
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#components-object
func NewComponents() Components
func (components Components) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Components.
func (components *Components) UnmarshalJSON(data []byte) error
UnmarshalJSON sets Components to a copy of data.
func (components *Components) Validate(ctx context.Context, opts ...ValidationOption) (err error)
Validate returns an error if Components does not comply with the OpenAPI
spec.
type Contact struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
URL string `json:"url,omitempty" yaml:"url,omitempty"`
Email string `json:"email,omitempty" yaml:"email,omitempty"`
}
Contact is specified by OpenAPI/Swagger standard version 3. See
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#contact-object
func (contact Contact) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Contact.
func (contact *Contact) UnmarshalJSON(data []byte) error
UnmarshalJSON sets Contact to a copy of data.
func (contact *Contact) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if Contact does not comply with the OpenAPI spec.
type Content map[string]*MediaType
Content is specified by OpenAPI/Swagger 3.0 standard.
func NewContent() Content
func NewContentWithFormDataSchema(schema *Schema) Content
func NewContentWithFormDataSchemaRef(schema *SchemaRef) Content
func NewContentWithJSONSchema(schema *Schema) Content
func NewContentWithJSONSchemaRef(schema *SchemaRef) Content
func NewContentWithSchema(schema *Schema, consumes []string) Content
func NewContentWithSchemaRef(schema *SchemaRef, consumes []string) Content
func (content Content) Get(mime string) *MediaType
func (content Content) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if Content does not comply with the OpenAPI spec.
type Discriminator struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
PropertyName string `json:"propertyName" yaml:"propertyName"` // required
Mapping map[string]string `json:"mapping,omitempty" yaml:"mapping,omitempty"`
}
Discriminator is specified by OpenAPI/Swagger standard version 3. See
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#discriminator-object
func (discriminator Discriminator) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Discriminator.
func (discriminator *Discriminator) UnmarshalJSON(data []byte) error
UnmarshalJSON sets Discriminator to a copy of data.
func (discriminator *Discriminator) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if Discriminator does not comply with the OpenAPI
spec.
type Encoding struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
ContentType string `json:"contentType,omitempty" yaml:"contentType,omitempty"`
Headers Headers `json:"headers,omitempty" yaml:"headers,omitempty"`
Style string `json:"style,omitempty" yaml:"style,omitempty"`
Explode *bool `json:"explode,omitempty" yaml:"explode,omitempty"`
AllowReserved bool `json:"allowReserved,omitempty" yaml:"allowReserved,omitempty"`
}
Encoding is specified by OpenAPI/Swagger 3.0 standard. See
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#encoding-object
func NewEncoding() *Encoding
func (encoding Encoding) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Encoding.
func (encoding *Encoding) SerializationMethod() *SerializationMethod
SerializationMethod returns a serialization method of request body.
When serialization method is not defined the method returns the default
serialization method.
func (encoding *Encoding) UnmarshalJSON(data []byte) error
UnmarshalJSON sets Encoding to a copy of data.
func (encoding *Encoding) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if Encoding does not comply with the OpenAPI spec.
func (encoding *Encoding) WithHeader(name string, header *Header) *Encoding
func (encoding *Encoding) WithHeaderRef(name string, ref *HeaderRef) *Encoding
type Example struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Summary string `json:"summary,omitempty" yaml:"summary,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Value interface{} `json:"value,omitempty" yaml:"value,omitempty"`
ExternalValue string `json:"externalValue,omitempty" yaml:"externalValue,omitempty"`
}
Example is specified by OpenAPI/Swagger 3.0 standard. See
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#example-object
func NewExample(value interface{}) *Example
func (example Example) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Example.
func (example *Example) UnmarshalJSON(data []byte) error
UnmarshalJSON sets Example to a copy of data.
func (example *Example) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if Example does not comply with the OpenAPI spec.
type ExampleRef struct {
Ref string
Value *Example
// Has unexported fields.
}
ExampleRef represents either a Example or a $ref to a Example. When
serializing and both fields are set, Ref is preferred over Value.
func (x *ExampleRef) JSONLookup(token string) (interface{}, error)
JSONLookup implements
https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (x ExampleRef) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of ExampleRef.
func (x ExampleRef) MarshalYAML() (interface{}, error)
MarshalYAML returns the YAML encoding of ExampleRef.
func (x *ExampleRef) UnmarshalJSON(data []byte) error
UnmarshalJSON sets ExampleRef to a copy of data.
func (x *ExampleRef) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if ExampleRef does not comply with the OpenAPI
spec.
type Examples map[string]*ExampleRef
func (m Examples) JSONLookup(token string) (interface{}, error)
JSONLookup implements
https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
type ExternalDocs struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
URL string `json:"url,omitempty" yaml:"url,omitempty"`
}
ExternalDocs is specified by OpenAPI/Swagger standard version 3. See
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#external-documentation-object
func (e ExternalDocs) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of ExternalDocs.
func (e *ExternalDocs) UnmarshalJSON(data []byte) error
UnmarshalJSON sets ExternalDocs to a copy of data.
func (e *ExternalDocs) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if ExternalDocs does not comply with the OpenAPI
spec.
type Format struct {
// Has unexported fields.
}
Format represents a format validator registered by either DefineStringFormat
or DefineStringFormatCallback
type FormatCallback func(value string) error
FormatCallback performs custom checks on exotic formats
type Header struct {
Parameter
}
Header is specified by OpenAPI/Swagger 3.0 standard. See
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#header-object
func (header Header) JSONLookup(token string) (interface{}, error)
JSONLookup implements
https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (header Header) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Header.
func (header Header) MarshalYAML() (interface{}, error)
MarshalYAML returns the JSON encoding of Header.
func (header *Header) SerializationMethod() (*SerializationMethod, error)
SerializationMethod returns a header's serialization method.
func (header *Header) UnmarshalJSON(data []byte) error
UnmarshalJSON sets Header to a copy of data.
func (header *Header) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if Header does not comply with the OpenAPI spec.
type HeaderRef struct {
Ref string
Value *Header
// Has unexported fields.
}
HeaderRef represents either a Header or a $ref to a Header. When serializing
and both fields are set, Ref is preferred over Value.
func (x *HeaderRef) JSONLookup(token string) (interface{}, error)
JSONLookup implements
https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (x HeaderRef) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of HeaderRef.
func (x HeaderRef) MarshalYAML() (interface{}, error)
MarshalYAML returns the YAML encoding of HeaderRef.
func (x *HeaderRef) UnmarshalJSON(data []byte) error
UnmarshalJSON sets HeaderRef to a copy of data.
func (x *HeaderRef) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if HeaderRef does not comply with the OpenAPI
spec.
type Headers map[string]*HeaderRef
func (m Headers) JSONLookup(token string) (interface{}, error)
JSONLookup implements
https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
type Info struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Title string `json:"title" yaml:"title"` // Required
Description string `json:"description,omitempty" yaml:"description,omitempty"`
TermsOfService string `json:"termsOfService,omitempty" yaml:"termsOfService,omitempty"`
Contact *Contact `json:"contact,omitempty" yaml:"contact,omitempty"`
License *License `json:"license,omitempty" yaml:"license,omitempty"`
Version string `json:"version" yaml:"version"` // Required
}
Info is specified by OpenAPI/Swagger standard version 3. See
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#info-object
func (info Info) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Info.
func (info *Info) UnmarshalJSON(data []byte) error
UnmarshalJSON sets Info to a copy of data.
func (info *Info) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if Info does not comply with the OpenAPI spec.
type License struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Name string `json:"name" yaml:"name"` // Required
URL string `json:"url,omitempty" yaml:"url,omitempty"`
}
License is specified by OpenAPI/Swagger standard version 3. See
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#license-object
func (license License) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of License.
func (license *License) UnmarshalJSON(data []byte) error
UnmarshalJSON sets License to a copy of data.
func (license *License) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if License does not comply with the OpenAPI spec.
type Link struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
OperationRef string `json:"operationRef,omitempty" yaml:"operationRef,omitempty"`
OperationID string `json:"operationId,omitempty" yaml:"operationId,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Parameters map[string]interface{} `json:"parameters,omitempty" yaml:"parameters,omitempty"`
Server *Server `json:"server,omitempty" yaml:"server,omitempty"`
RequestBody interface{} `json:"requestBody,omitempty" yaml:"requestBody,omitempty"`
}
Link is specified by OpenAPI/Swagger standard version 3. See
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#link-object
func (link Link) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Link.
func (link *Link) UnmarshalJSON(data []byte) error
UnmarshalJSON sets Link to a copy of data.
func (link *Link) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if Link does not comply with the OpenAPI spec.
type LinkRef struct {
Ref string
Value *Link
// Has unexported fields.
}
LinkRef represents either a Link or a $ref to a Link. When serializing and
both fields are set, Ref is preferred over Value.
func (x *LinkRef) JSONLookup(token string) (interface{}, error)
JSONLookup implements
https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (x LinkRef) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of LinkRef.
func (x LinkRef) MarshalYAML() (interface{}, error)
MarshalYAML returns the YAML encoding of LinkRef.
func (x *LinkRef) UnmarshalJSON(data []byte) error
UnmarshalJSON sets LinkRef to a copy of data.
func (x *LinkRef) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if LinkRef does not comply with the OpenAPI spec.
type Links map[string]*LinkRef
func (m Links) JSONLookup(token string) (interface{}, error)
JSONLookup implements
https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
type Loader struct {
// IsExternalRefsAllowed enables visiting other files
IsExternalRefsAllowed bool
// ReadFromURIFunc allows overriding the any file/URL reading func
ReadFromURIFunc ReadFromURIFunc
Context context.Context
// Has unexported fields.
}
Loader helps deserialize an OpenAPIv3 document
func NewLoader() *Loader
NewLoader returns an empty Loader
func (loader *Loader) LoadFromData(data []byte) (*T, error)
LoadFromData loads a spec from a byte array
func (loader *Loader) LoadFromDataWithPath(data []byte, location *url.URL) (*T, error)
LoadFromDataWithPath takes the OpenAPI document data in bytes and a path
where the resolver can find referred elements and returns a *T with all
resolved data or an error if unable to load data or resolve refs.
func (loader *Loader) LoadFromFile(location string) (*T, error)
LoadFromFile loads a spec from a local file path
func (loader *Loader) LoadFromIoReader(reader io.Reader) (*T, error)
LoadFromStdin loads a spec from io.Reader
func (loader *Loader) LoadFromStdin() (*T, error)
LoadFromStdin loads a spec from stdin
func (loader *Loader) LoadFromURI(location *url.URL) (*T, error)
LoadFromURI loads a spec from a remote URL
func (loader *Loader) ResolveRefsIn(doc *T, location *url.URL) (err error)
ResolveRefsIn expands references if for instance spec was just unmarshaled
type MediaType struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Schema *SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"`
Example interface{} `json:"example,omitempty" yaml:"example,omitempty"`
Examples Examples `json:"examples,omitempty" yaml:"examples,omitempty"`
Encoding map[string]*Encoding `json:"encoding,omitempty" yaml:"encoding,omitempty"`
}
MediaType is specified by OpenAPI/Swagger 3.0 standard. See
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#media-type-object
func NewMediaType() *MediaType
func (mediaType MediaType) JSONLookup(token string) (interface{}, error)
JSONLookup implements
https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (mediaType MediaType) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of MediaType.
func (mediaType *MediaType) UnmarshalJSON(data []byte) error
UnmarshalJSON sets MediaType to a copy of data.
func (mediaType *MediaType) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if MediaType does not comply with the OpenAPI
spec.
func (mediaType *MediaType) WithEncoding(name string, enc *Encoding) *MediaType
func (mediaType *MediaType) WithExample(name string, value interface{}) *MediaType
func (mediaType *MediaType) WithSchema(schema *Schema) *MediaType
func (mediaType *MediaType) WithSchemaRef(schema *SchemaRef) *MediaType
type MultiError []error
MultiError is a collection of errors, intended for when multiple issues need
to be reported upstream
func (me MultiError) As(target interface{}) bool
As allows you to use `errors.As()` to set target to the first error within
the multi error that matches the target type
func (me MultiError) Error() string
func (me MultiError) Is(target error) bool
Is allows you to determine if a generic error is in fact a MultiError using
`errors.Is()` It will also return true if any of the contained errors match
target
type NewCallbackOption func(*Callback)
NewCallbackOption describes options to NewCallback func
func WithCallback(cb string, pathItem *PathItem) NewCallbackOption
WithCallback adds Callback as an option to NewCallback
type NewPathsOption func(*Paths)
NewPathsOption describes options to NewPaths func
func WithPath(path string, pathItem *PathItem) NewPathsOption
WithPath adds a named path item
type NewResponsesOption func(*Responses)
NewResponsesOption describes options to NewResponses func
func WithName(name string, response *Response) NewResponsesOption
WithName adds a name-keyed Response
func WithStatus(status int, responseRef *ResponseRef) NewResponsesOption
WithStatus adds a status code keyed ResponseRef
type OAuthFlow struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
AuthorizationURL string `json:"authorizationUrl,omitempty" yaml:"authorizationUrl,omitempty"`
TokenURL string `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"`
RefreshURL string `json:"refreshUrl,omitempty" yaml:"refreshUrl,omitempty"`
Scopes map[string]string `json:"scopes" yaml:"scopes"` // required
}
OAuthFlow is specified by OpenAPI/Swagger standard version 3. See
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauth-flow-object
func (flow OAuthFlow) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of OAuthFlow.
func (flow *OAuthFlow) UnmarshalJSON(data []byte) error
UnmarshalJSON sets OAuthFlow to a copy of data.
func (flow *OAuthFlow) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if OAuthFlows does not comply with the OpenAPI
spec.
type OAuthFlows struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Implicit *OAuthFlow `json:"implicit,omitempty" yaml:"implicit,omitempty"`
Password *OAuthFlow `json:"password,omitempty" yaml:"password,omitempty"`
ClientCredentials *OAuthFlow `json:"clientCredentials,omitempty" yaml:"clientCredentials,omitempty"`
AuthorizationCode *OAuthFlow `json:"authorizationCode,omitempty" yaml:"authorizationCode,omitempty"`
}
OAuthFlows is specified by OpenAPI/Swagger standard version 3. See
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauth-flows-object
func (flows OAuthFlows) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of OAuthFlows.
func (flows *OAuthFlows) UnmarshalJSON(data []byte) error
UnmarshalJSON sets OAuthFlows to a copy of data.
func (flows *OAuthFlows) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if OAuthFlows does not comply with the OpenAPI
spec.
type Operation struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
// Optional tags for documentation.
Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"`
// Optional short summary.
Summary string `json:"summary,omitempty" yaml:"summary,omitempty"`
// Optional description. Should use CommonMark syntax.
Description string `json:"description,omitempty" yaml:"description,omitempty"`
// Optional operation ID.
OperationID string `json:"operationId,omitempty" yaml:"operationId,omitempty"`
// Optional parameters.
Parameters Parameters `json:"parameters,omitempty" yaml:"parameters,omitempty"`
// Optional body parameter.
RequestBody *RequestBodyRef `json:"requestBody,omitempty" yaml:"requestBody,omitempty"`
// Responses.
Responses *Responses `json:"responses" yaml:"responses"` // Required
// Optional callbacks
Callbacks Callbacks `json:"callbacks,omitempty" yaml:"callbacks,omitempty"`
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
// Optional security requirements that overrides top-level security.
Security *SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"`
// Optional servers that overrides top-level servers.
Servers *Servers `json:"servers,omitempty" yaml:"servers,omitempty"`
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
}
Operation represents "operation" specified
by" OpenAPI/Swagger 3.0 standard. See
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#operation-object
func NewOperation() *Operation
func (operation *Operation) AddParameter(p *Parameter)
func (operation *Operation) AddResponse(status int, response *Response)
func (operation Operation) JSONLookup(token string) (interface{}, error)
JSONLookup implements
https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (operation Operation) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Operation.
func (operation *Operation) UnmarshalJSON(data []byte) error
UnmarshalJSON sets Operation to a copy of data.
func (operation *Operation) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if Operation does not comply with the OpenAPI
spec.
type Parameter struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
In string `json:"in,omitempty" yaml:"in,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Style string `json:"style,omitempty" yaml:"style,omitempty"`
Explode *bool `json:"explode,omitempty" yaml:"explode,omitempty"`
AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"`
AllowReserved bool `json:"allowReserved,omitempty" yaml:"allowReserved,omitempty"`
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
Required bool `json:"required,omitempty" yaml:"required,omitempty"`
Schema *SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"`
Example interface{} `json:"example,omitempty" yaml:"example,omitempty"`
Examples Examples `json:"examples,omitempty" yaml:"examples,omitempty"`
Content Content `json:"content,omitempty" yaml:"content,omitempty"`
}
Parameter is specified by OpenAPI/Swagger 3.0 standard. See
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#parameter-object
func NewCookieParameter(name string) *Parameter
func NewHeaderParameter(name string) *Parameter
func NewPathParameter(name string) *Parameter
func NewQueryParameter(name string) *Parameter
func (parameter Parameter) JSONLookup(token string) (interface{}, error)
JSONLookup implements
https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (parameter Parameter) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Parameter.
func (parameter *Parameter) SerializationMethod() (*SerializationMethod, error)
SerializationMethod returns a parameter's serialization method. When a
parameter's serialization method is not defined the method returns the
default serialization method corresponding to a parameter's location.
func (parameter *Parameter) UnmarshalJSON(data []byte) error
UnmarshalJSON sets Parameter to a copy of data.
func (parameter *Parameter) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if Parameter does not comply with the OpenAPI
spec.
func (parameter *Parameter) WithDescription(value string) *Parameter
func (parameter *Parameter) WithRequired(value bool) *Parameter
func (parameter *Parameter) WithSchema(value *Schema) *Parameter
type ParameterRef struct {
Ref string
Value *Parameter
// Has unexported fields.
}
ParameterRef represents either a Parameter or a $ref to a Parameter.
When serializing and both fields are set, Ref is preferred over Value.
func (x *ParameterRef) JSONLookup(token string) (interface{}, error)
JSONLookup implements
https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (x ParameterRef) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of ParameterRef.
func (x ParameterRef) MarshalYAML() (interface{}, error)
MarshalYAML returns the YAML encoding of ParameterRef.
func (x *ParameterRef) UnmarshalJSON(data []byte) error
UnmarshalJSON sets ParameterRef to a copy of data.
func (x *ParameterRef) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if ParameterRef does not comply with the OpenAPI
spec.
type Parameters []*ParameterRef
Parameters is specified by OpenAPI/Swagger 3.0 standard.
func NewParameters() Parameters
func (parameters Parameters) GetByInAndName(in string, name string) *Parameter
func (p Parameters) JSONLookup(token string) (interface{}, error)
JSONLookup implements
https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (parameters Parameters) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if Parameters does not comply with the OpenAPI
spec.
type ParametersMap map[string]*ParameterRef
func (m ParametersMap) JSONLookup(token string) (interface{}, error)
JSONLookup implements
https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
type PathItem struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
Summary string `json:"summary,omitempty" yaml:"summary,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Connect *Operation `json:"connect,omitempty" yaml:"connect,omitempty"`
Delete *Operation `json:"delete,omitempty" yaml:"delete,omitempty"`
Get *Operation `json:"get,omitempty" yaml:"get,omitempty"`
Head *Operation `json:"head,omitempty" yaml:"head,omitempty"`
Options *Operation `json:"options,omitempty" yaml:"options,omitempty"`
Patch *Operation `json:"patch,omitempty" yaml:"patch,omitempty"`
Post *Operation `json:"post,omitempty" yaml:"post,omitempty"`
Put *Operation `json:"put,omitempty" yaml:"put,omitempty"`
Trace *Operation `json:"trace,omitempty" yaml:"trace,omitempty"`
Servers Servers `json:"servers,omitempty" yaml:"servers,omitempty"`
Parameters Parameters `json:"parameters,omitempty" yaml:"parameters,omitempty"`
}
PathItem is specified by OpenAPI/Swagger standard version 3. See
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#path-item-object
func (pathItem *PathItem) GetOperation(method string) *Operation
func (pathItem PathItem) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of PathItem.
func (pathItem *PathItem) Operations() map[string]*Operation
func (pathItem *PathItem) SetOperation(method string, operation *Operation)
func (pathItem *PathItem) UnmarshalJSON(data []byte) error
UnmarshalJSON sets PathItem to a copy of data.
func (pathItem *PathItem) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if PathItem does not comply with the OpenAPI spec.
type Paths struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
// Has unexported fields.
}
Paths is specified by OpenAPI/Swagger standard version 3. See
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#paths-object
func NewPaths(opts ...NewPathsOption) *Paths
NewPaths builds a paths object with path items in insertion order.
func NewPathsWithCapacity(cap int) *Paths
NewPathsWithCapacity builds a paths object of the given capacity.
func (paths *Paths) Delete(key string)
Delete removes the entry associated with key 'key' from 'paths'.
func (paths *Paths) Find(key string) *PathItem
Find returns a path that matches the key.
The method ignores differences in template variable names (except possible
"*" suffix).
For example:
paths := openapi3.Paths {
"/person/{personName}": &openapi3.PathItem{},
}
pathItem := path.Find("/person/{name}")
would return the correct path item.
func (paths *Paths) InMatchingOrder() []string
InMatchingOrder returns paths in the
order they are matched against URLs. See
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#paths-object
When matching URLs, concrete (non-templated) paths would be matched before
their templated counterparts.
func (paths Paths) JSONLookup(token string) (interface{}, error)
JSONLookup implements
https://github.com/go-openapi/jsonpointer#JSONPointable
func (paths *Paths) Len() int
Len returns the amount of keys in paths excluding paths.Extensions.
func (paths *Paths) Map() (m map[string]*PathItem)
Map returns paths as a 'map'. Note: iteration on Go maps is not ordered.
func (paths *Paths) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Paths.
func (paths *Paths) MarshalYAML() (any, error)
Support YAML Marshaler interface for gopkg.in/yaml
func (paths *Paths) Set(key string, value *PathItem)
Set adds or replaces key 'key' of 'paths' with 'value'. Note: 'paths' MUST
be non-nil
func (paths *Paths) UnmarshalJSON(data []byte) (err error)
UnmarshalJSON sets Paths to a copy of data.
func (paths *Paths) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if Paths does not comply with the OpenAPI spec.
func (paths *Paths) Value(key string) *PathItem
Value returns the paths for key or nil
type ReadFromURIFunc func(loader *Loader, url *url.URL) ([]byte, error)
ReadFromURIFunc defines a function which reads the contents of a resource
located at a URI.
func ReadFromHTTP(cl *http.Client) ReadFromURIFunc
ReadFromHTTP returns a ReadFromURIFunc which uses the given http.Client to
read the contents from a remote HTTP URI. This client may be customized to
implement timeouts, RFC 7234 caching, etc.
func ReadFromURIs(readers ...ReadFromURIFunc) ReadFromURIFunc
ReadFromURIs returns a ReadFromURIFunc which tries to read a URI using the
given reader functions, in the same order. If a reader function does not
support the URI and returns ErrURINotSupported, the next function is checked
until a match is found, or the URI is not supported by any.
func URIMapCache(reader ReadFromURIFunc) ReadFromURIFunc
URIMapCache returns a ReadFromURIFunc that caches the contents read from
URI locations in a simple map. This cache implementation is suitable for
short-lived processes such as command-line tools which process OpenAPI
documents.
type Ref struct {
Ref string `json:"$ref" yaml:"$ref"`
}
Ref is specified by OpenAPI/Swagger 3.0 standard. See
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#reference-object
type RefNameResolver func(string) string
type RequestBodies map[string]*RequestBodyRef
func (m RequestBodies) JSONLookup(token string) (interface{}, error)
JSONLookup implements
https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
type RequestBody struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Required bool `json:"required,omitempty" yaml:"required,omitempty"`
Content Content `json:"content" yaml:"content"`
}
RequestBody is specified by OpenAPI/Swagger 3.0 standard. See
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#request-body-object
func NewRequestBody() *RequestBody
func (requestBody *RequestBody) GetMediaType(mediaType string) *MediaType
func (requestBody RequestBody) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of RequestBody.
func (requestBody *RequestBody) UnmarshalJSON(data []byte) error
UnmarshalJSON sets RequestBody to a copy of data.
func (requestBody *RequestBody) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if RequestBody does not comply with the OpenAPI
spec.
func (requestBody *RequestBody) WithContent(content Content) *RequestBody
func (requestBody *RequestBody) WithDescription(value string) *RequestBody
func (requestBody *RequestBody) WithFormDataSchema(value *Schema) *RequestBody
func (requestBody *RequestBody) WithFormDataSchemaRef(value *SchemaRef) *RequestBody
func (requestBody *RequestBody) WithJSONSchema(value *Schema) *RequestBody
func (requestBody *RequestBody) WithJSONSchemaRef(value *SchemaRef) *RequestBody
func (requestBody *RequestBody) WithRequired(value bool) *RequestBody
func (requestBody *RequestBody) WithSchema(value *Schema, consumes []string) *RequestBody
func (requestBody *RequestBody) WithSchemaRef(value *SchemaRef, consumes []string) *RequestBody
type RequestBodyRef struct {
Ref string
Value *RequestBody
// Has unexported fields.
}
RequestBodyRef represents either a RequestBody or a $ref to a RequestBody.
When serializing and both fields are set, Ref is preferred over Value.
func (x *RequestBodyRef) JSONLookup(token string) (interface{}, error)
JSONLookup implements
https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (x RequestBodyRef) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of RequestBodyRef.
func (x RequestBodyRef) MarshalYAML() (interface{}, error)
MarshalYAML returns the YAML encoding of RequestBodyRef.
func (x *RequestBodyRef) UnmarshalJSON(data []byte) error
UnmarshalJSON sets RequestBodyRef to a copy of data.
func (x *RequestBodyRef) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if RequestBodyRef does not comply with the OpenAPI
spec.
type Response struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Description *string `json:"description,omitempty" yaml:"description,omitempty"`
Headers Headers `json:"headers,omitempty" yaml:"headers,omitempty"`
Content Content `json:"content,omitempty" yaml:"content,omitempty"`
Links Links `json:"links,omitempty" yaml:"links,omitempty"`
}
Response is specified by OpenAPI/Swagger 3.0 standard. See
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#response-object
func NewResponse() *Response
func (response Response) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Response.
func (response *Response) UnmarshalJSON(data []byte) error
UnmarshalJSON sets Response to a copy of data.
func (response *Response) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if Response does not comply with the OpenAPI spec.
func (response *Response) WithContent(content Content) *Response
func (response *Response) WithDescription(value string) *Response
func (response *Response) WithJSONSchema(schema *Schema) *Response
func (response *Response) WithJSONSchemaRef(schema *SchemaRef) *Response
type ResponseBodies map[string]*ResponseRef
func (m ResponseBodies) JSONLookup(token string) (interface{}, error)
JSONLookup implements
https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
type ResponseRef struct {
Ref string
Value *Response
// Has unexported fields.
}
ResponseRef represents either a Response or a $ref to a Response. When
serializing and both fields are set, Ref is preferred over Value.
func (x *ResponseRef) JSONLookup(token string) (interface{}, error)
JSONLookup implements
https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (x ResponseRef) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of ResponseRef.
func (x ResponseRef) MarshalYAML() (interface{}, error)
MarshalYAML returns the YAML encoding of ResponseRef.
func (x *ResponseRef) UnmarshalJSON(data []byte) error
UnmarshalJSON sets ResponseRef to a copy of data.
func (x *ResponseRef) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if ResponseRef does not comply with the OpenAPI
spec.
type Responses struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
// Has unexported fields.
}
Responses is specified by OpenAPI/Swagger 3.0 standard. See
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#responses-object
func NewResponses(opts ...NewResponsesOption) *Responses
NewResponses builds a responses object with response objects in insertion
order. Given no arguments, NewResponses returns a valid responses object
containing a default match-all reponse.
func NewResponsesWithCapacity(cap int) *Responses
NewResponsesWithCapacity builds a responses object of the given capacity.
func (responses *Responses) Default() *ResponseRef
Default returns the default response
func (responses *Responses) Delete(key string)
Delete removes the entry associated with key 'key' from 'responses'.
func (responses Responses) JSONLookup(token string) (interface{}, error)
JSONLookup implements
https://github.com/go-openapi/jsonpointer#JSONPointable
func (responses *Responses) Len() int
Len returns the amount of keys in responses excluding responses.Extensions.
func (responses *Responses) Map() (m map[string]*ResponseRef)
Map returns responses as a 'map'. Note: iteration on Go maps is not ordered.
func (responses *Responses) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Responses.
func (responses *Responses) MarshalYAML() (any, error)
Support YAML Marshaler interface for gopkg.in/yaml
func (responses *Responses) Set(key string, value *ResponseRef)
Set adds or replaces key 'key' of 'responses' with 'value'. Note:
'responses' MUST be non-nil
func (responses *Responses) Status(status int) *ResponseRef
Status returns a ResponseRef for the given status If an exact
match isn't initially found a patterned field is checked using
the first digit to determine the range (eg: 201 to 2XX) See
https://spec.openapis.org/oas/v3.0.3#patterned-fields-0
func (responses *Responses) UnmarshalJSON(data []byte) (err error)
UnmarshalJSON sets Responses to a copy of data.
func (responses *Responses) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if Responses does not comply with the OpenAPI
spec.
func (responses *Responses) Value(key string) *ResponseRef
Value returns the responses for key or nil
type Schema struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
OneOf SchemaRefs `json:"oneOf,omitempty" yaml:"oneOf,omitempty"`
AnyOf SchemaRefs `json:"anyOf,omitempty" yaml:"anyOf,omitempty"`
AllOf SchemaRefs `json:"allOf,omitempty" yaml:"allOf,omitempty"`
Not *SchemaRef `json:"not,omitempty" yaml:"not,omitempty"`
Type *Types `json:"type,omitempty" yaml:"type,omitempty"`
Title string `json:"title,omitempty" yaml:"title,omitempty"`
Format string `json:"format,omitempty" yaml:"format,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Enum []interface{} `json:"enum,omitempty" yaml:"enum,omitempty"`
Default interface{} `json:"default,omitempty" yaml:"default,omitempty"`
Example interface{} `json:"example,omitempty" yaml:"example,omitempty"`
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
// Array-related, here for struct compactness
UniqueItems bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"`
// Number-related, here for struct compactness
ExclusiveMin bool `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"`
ExclusiveMax bool `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"`
// Properties
Nullable bool `json:"nullable,omitempty" yaml:"nullable,omitempty"`
ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"`
WriteOnly bool `json:"writeOnly,omitempty" yaml:"writeOnly,omitempty"`
AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"`
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
XML *XML `json:"xml,omitempty" yaml:"xml,omitempty"`
// Number
Min *float64 `json:"minimum,omitempty" yaml:"minimum,omitempty"`
Max *float64 `json:"maximum,omitempty" yaml:"maximum,omitempty"`
MultipleOf *float64 `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"`
// String
MinLength uint64 `json:"minLength,omitempty" yaml:"minLength,omitempty"`
MaxLength *uint64 `json:"maxLength,omitempty" yaml:"maxLength,omitempty"`
Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"`
// Array
MinItems uint64 `json:"minItems,omitempty" yaml:"minItems,omitempty"`
MaxItems *uint64 `json:"maxItems,omitempty" yaml:"maxItems,omitempty"`
Items *SchemaRef `json:"items,omitempty" yaml:"items,omitempty"`
// Object
Required []string `json:"required,omitempty" yaml:"required,omitempty"`
Properties Schemas `json:"properties,omitempty" yaml:"properties,omitempty"`
MinProps uint64 `json:"minProperties,omitempty" yaml:"minProperties,omitempty"`
MaxProps *uint64 `json:"maxProperties,omitempty" yaml:"maxProperties,omitempty"`
AdditionalProperties AdditionalProperties `json:"additionalProperties,omitempty" yaml:"additionalProperties,omitempty"`
Discriminator *Discriminator `json:"discriminator,omitempty" yaml:"discriminator,omitempty"`
}
Schema is specified by OpenAPI/Swagger 3.0 standard. See
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#schema-object
func NewAllOfSchema(schemas ...*Schema) *Schema
func NewAnyOfSchema(schemas ...*Schema) *Schema
func NewArraySchema() *Schema
func NewBoolSchema() *Schema
func NewBytesSchema() *Schema
func NewDateTimeSchema() *Schema
func NewFloat64Schema() *Schema
func NewInt32Schema() *Schema
func NewInt64Schema() *Schema
func NewIntegerSchema() *Schema
func NewObjectSchema() *Schema
func NewOneOfSchema(schemas ...*Schema) *Schema
func NewSchema() *Schema
func NewStringSchema() *Schema
func NewUUIDSchema() *Schema
func (schema *Schema) IsEmpty() bool
IsEmpty tells whether schema is equivalent to the empty schema `{}`.
func (schema *Schema) IsMatching(value interface{}) bool
func (schema *Schema) IsMatchingJSONArray(value []interface{}) bool
func (schema *Schema) IsMatchingJSONBoolean(value bool) bool
func (schema *Schema) IsMatchingJSONNumber(value float64) bool
func (schema *Schema) IsMatchingJSONObject(value map[string]interface{}) bool
func (schema *Schema) IsMatchingJSONString(value string) bool
func (schema Schema) JSONLookup(token string) (interface{}, error)
JSONLookup implements
https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (schema Schema) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Schema.
func (schema *Schema) NewRef() *SchemaRef
func (schema *Schema) PermitsNull() bool
func (schema *Schema) UnmarshalJSON(data []byte) error
UnmarshalJSON sets Schema to a copy of data.
func (schema *Schema) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if Schema does not comply with the OpenAPI spec.
func (schema *Schema) VisitJSON(value interface{}, opts ...SchemaValidationOption) error
func (schema *Schema) VisitJSONArray(value []interface{}) error
func (schema *Schema) VisitJSONBoolean(value bool) error
func (schema *Schema) VisitJSONNumber(value float64) error
func (schema *Schema) VisitJSONObject(value map[string]interface{}) error
func (schema *Schema) VisitJSONString(value string) error
func (schema *Schema) WithAdditionalProperties(v *Schema) *Schema
func (schema *Schema) WithAnyAdditionalProperties() *Schema
func (schema *Schema) WithDefault(defaultValue interface{}) *Schema
func (schema *Schema) WithEnum(values ...interface{}) *Schema
func (schema *Schema) WithExclusiveMax(value bool) *Schema
func (schema *Schema) WithExclusiveMin(value bool) *Schema
func (schema *Schema) WithFormat(value string) *Schema
func (schema *Schema) WithItems(value *Schema) *Schema
func (schema *Schema) WithLength(i int64) *Schema
func (schema *Schema) WithLengthDecodedBase64(i int64) *Schema
func (schema *Schema) WithMax(value float64) *Schema
func (schema *Schema) WithMaxItems(i int64) *Schema
func (schema *Schema) WithMaxLength(i int64) *Schema
func (schema *Schema) WithMaxLengthDecodedBase64(i int64) *Schema
func (schema *Schema) WithMaxProperties(i int64) *Schema
func (schema *Schema) WithMin(value float64) *Schema
func (schema *Schema) WithMinItems(i int64) *Schema
func (schema *Schema) WithMinLength(i int64) *Schema
func (schema *Schema) WithMinLengthDecodedBase64(i int64) *Schema
func (schema *Schema) WithMinProperties(i int64) *Schema
func (schema *Schema) WithNullable() *Schema
func (schema *Schema) WithPattern(pattern string) *Schema
func (schema *Schema) WithProperties(properties map[string]*Schema) *Schema
func (schema *Schema) WithProperty(name string, propertySchema *Schema) *Schema
func (schema *Schema) WithPropertyRef(name string, ref *SchemaRef) *Schema
func (schema *Schema) WithRequired(required []string) *Schema
func (schema *Schema) WithUniqueItems(unique bool) *Schema
func (schema *Schema) WithoutAdditionalProperties() *Schema
type SchemaError struct {
// Value is the value that failed validation.
Value interface{}
// Schema is the schema that failed validation.
Schema *Schema
// SchemaField is the field of the schema that failed validation.
SchemaField string
// Reason is a human-readable message describing the error.
// The message should never include the original value to prevent leakage of potentially sensitive inputs in error messages.
Reason string
// Origin is the original error that caused this error.
Origin error
// Has unexported fields.
}
SchemaError is an error that occurs during schema validation.
func (err *SchemaError) Error() string
func (err *SchemaError) JSONPointer() []string
func (err SchemaError) Unwrap() error
type SchemaRef struct {
Ref string
Value *Schema
// Has unexported fields.
}
SchemaRef represents either a Schema or a $ref to a Schema. When serializing
and both fields are set, Ref is preferred over Value.
func NewSchemaRef(ref string, value *Schema) *SchemaRef
NewSchemaRef simply builds a SchemaRef
func (x *SchemaRef) JSONLookup(token string) (interface{}, error)
JSONLookup implements
https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (x SchemaRef) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of SchemaRef.
func (x SchemaRef) MarshalYAML() (interface{}, error)
MarshalYAML returns the YAML encoding of SchemaRef.
func (x *SchemaRef) UnmarshalJSON(data []byte) error
UnmarshalJSON sets SchemaRef to a copy of data.
func (x *SchemaRef) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if SchemaRef does not comply with the OpenAPI
spec.
type SchemaRefs []*SchemaRef
func (s SchemaRefs) JSONLookup(token string) (interface{}, error)
JSONLookup implements
https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
type SchemaValidationOption func(*schemaValidationSettings)
SchemaValidationOption describes options a user has when validating request
/ response bodies.
func DefaultsSet(f func()) SchemaValidationOption
DefaultsSet executes the given callback (once) IFF schema validation set
default values.
func DisablePatternValidation() SchemaValidationOption
DisablePatternValidation setting makes Validate not return an error when
validating patterns that are not supported by the Go regexp engine.
func DisableReadOnlyValidation() SchemaValidationOption
DisableReadOnlyValidation setting makes Validate not return an error when
validating properties marked as read-only
func DisableWriteOnlyValidation() SchemaValidationOption
DisableWriteOnlyValidation setting makes Validate not return an error when
validating properties marked as write-only
func EnableFormatValidation() SchemaValidationOption
EnableFormatValidation setting makes Validate not return an error when
validating documents that mention schema formats that are not defined by the
OpenAPIv3 specification.
func FailFast() SchemaValidationOption
FailFast returns schema validation errors quicker.
func MultiErrors() SchemaValidationOption
func SetSchemaErrorMessageCustomizer(f func(err *SchemaError) string) SchemaValidationOption
SetSchemaErrorMessageCustomizer allows to override the schema error message.
If the passed function returns an empty string, it returns to the previous
Error() implementation.
func VisitAsRequest() SchemaValidationOption
func VisitAsResponse() SchemaValidationOption
type Schemas map[string]*SchemaRef
func (m Schemas) JSONLookup(token string) (interface{}, error)
JSONLookup implements
https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
type SecurityRequirement map[string][]string
SecurityRequirement is specified by OpenAPI/Swagger standard version 3. See
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-requirement-object
func NewSecurityRequirement() SecurityRequirement
func (security SecurityRequirement) Authenticate(provider string, scopes ...string) SecurityRequirement
func (security *SecurityRequirement) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if SecurityRequirement does not comply with the
OpenAPI spec.
type SecurityRequirements []SecurityRequirement
func NewSecurityRequirements() *SecurityRequirements
func (srs SecurityRequirements) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if SecurityRequirements does not comply with the
OpenAPI spec.
func (srs *SecurityRequirements) With(securityRequirement SecurityRequirement) *SecurityRequirements
type SecurityScheme struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Type string `json:"type,omitempty" yaml:"type,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
In string `json:"in,omitempty" yaml:"in,omitempty"`
Scheme string `json:"scheme,omitempty" yaml:"scheme,omitempty"`
BearerFormat string `json:"bearerFormat,omitempty" yaml:"bearerFormat,omitempty"`
Flows *OAuthFlows `json:"flows,omitempty" yaml:"flows,omitempty"`
OpenIdConnectUrl string `json:"openIdConnectUrl,omitempty" yaml:"openIdConnectUrl,omitempty"`
}
SecurityScheme is specified by OpenAPI/Swagger standard version 3. See
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-scheme-object
func NewCSRFSecurityScheme() *SecurityScheme
func NewJWTSecurityScheme() *SecurityScheme
func NewOIDCSecurityScheme(oidcUrl string) *SecurityScheme
func NewSecurityScheme() *SecurityScheme
func (ss SecurityScheme) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of SecurityScheme.
func (ss *SecurityScheme) UnmarshalJSON(data []byte) error
UnmarshalJSON sets SecurityScheme to a copy of data.
func (ss *SecurityScheme) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if SecurityScheme does not comply with the OpenAPI
spec.
func (ss *SecurityScheme) WithBearerFormat(value string) *SecurityScheme
func (ss *SecurityScheme) WithDescription(value string) *SecurityScheme
func (ss *SecurityScheme) WithIn(value string) *SecurityScheme
func (ss *SecurityScheme) WithName(value string) *SecurityScheme
func (ss *SecurityScheme) WithScheme(value string) *SecurityScheme
func (ss *SecurityScheme) WithType(value string) *SecurityScheme
type SecuritySchemeRef struct {
Ref string
Value *SecurityScheme
// Has unexported fields.
}
SecuritySchemeRef represents either a SecurityScheme or a $ref to a
SecurityScheme. When serializing and both fields are set, Ref is preferred
over Value.
func (x *SecuritySchemeRef) JSONLookup(token string) (interface{}, error)
JSONLookup implements
https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (x SecuritySchemeRef) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of SecuritySchemeRef.
func (x SecuritySchemeRef) MarshalYAML() (interface{}, error)
MarshalYAML returns the YAML encoding of SecuritySchemeRef.
func (x *SecuritySchemeRef) UnmarshalJSON(data []byte) error
UnmarshalJSON sets SecuritySchemeRef to a copy of data.
func (x *SecuritySchemeRef) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if SecuritySchemeRef does not comply with the
OpenAPI spec.
type SecuritySchemes map[string]*SecuritySchemeRef
func (m SecuritySchemes) JSONLookup(token string) (interface{}, error)
JSONLookup implements
https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
type SerializationMethod struct {
Style string
Explode bool
}
SerializationMethod describes a serialization method of HTTP request's
parameters and body.
type Server struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
URL string `json:"url" yaml:"url"` // Required
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Variables map[string]*ServerVariable `json:"variables,omitempty" yaml:"variables,omitempty"`
}
Server is specified by OpenAPI/Swagger standard version 3. See
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#server-object
func (server *Server) BasePath() (string, error)
BasePath returns the base path extracted from the default values of
variables, if any. Assumes a valid struct (per Validate()).
func (server Server) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Server.
func (server Server) MatchRawURL(input string) ([]string, string, bool)
func (server Server) ParameterNames() ([]string, error)
func (server *Server) UnmarshalJSON(data []byte) error
UnmarshalJSON sets Server to a copy of data.
func (server *Server) Validate(ctx context.Context, opts ...ValidationOption) (err error)
Validate returns an error if Server does not comply with the OpenAPI spec.
type ServerVariable struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Enum []string `json:"enum,omitempty" yaml:"enum,omitempty"`
Default string `json:"default,omitempty" yaml:"default,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
}
ServerVariable is specified by OpenAPI/Swagger standard version 3. See
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#server-variable-object
func (serverVariable ServerVariable) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of ServerVariable.
func (serverVariable *ServerVariable) UnmarshalJSON(data []byte) error
UnmarshalJSON sets ServerVariable to a copy of data.
func (serverVariable *ServerVariable) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if ServerVariable does not comply with the OpenAPI
spec.
type Servers []*Server
Servers is specified by OpenAPI/Swagger standard version 3.
func (servers Servers) BasePath() (string, error)
BasePath returns the base path of the first server in the list, or /.
func (servers Servers) MatchURL(parsedURL *url.URL) (*Server, []string, string)
func (servers Servers) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if Servers does not comply with the OpenAPI spec.
type SliceUniqueItemsChecker func(items []interface{}) bool
SliceUniqueItemsChecker is an function used to check if an given slice have
unique items.
type T struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
OpenAPI string `json:"openapi" yaml:"openapi"` // Required
Components *Components `json:"components,omitempty" yaml:"components,omitempty"`
Info *Info `json:"info" yaml:"info"` // Required
Paths *Paths `json:"paths" yaml:"paths"` // Required
Security SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"`
Servers Servers `json:"servers,omitempty" yaml:"servers,omitempty"`
Tags Tags `json:"tags,omitempty" yaml:"tags,omitempty"`
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
// Has unexported fields.
}
T is the root of an OpenAPI v3 document See
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#openapi-object
func (doc *T) AddOperation(path string, method string, operation *Operation)
func (doc *T) AddServer(server *Server)
func (doc *T) AddServers(servers ...*Server)
func (doc *T) InternalizeRefs(ctx context.Context, refNameResolver func(ref string) string)
InternalizeRefs removes all references to external files from the spec and
moves them to the components section.
refNameResolver takes in references to returns a name to store the reference
under locally. It MUST return a unique name for each reference type.
A default implementation is provided that will suffice for most use cases.
See the function documentation for more details.
Example:
doc.InternalizeRefs(context.Background(), nil)
func (doc *T) JSONLookup(token string) (interface{}, error)
JSONLookup implements
https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (doc *T) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of T.
func (doc *T) UnmarshalJSON(data []byte) error
UnmarshalJSON sets T to a copy of data.
func (doc *T) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if T does not comply with the OpenAPI spec.
Validations Options can be provided to modify the validation behavior.
type Tag struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
}
Tag is specified by OpenAPI/Swagger 3.0 standard. See
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#tag-object
func (t Tag) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Tag.
func (t *Tag) UnmarshalJSON(data []byte) error
UnmarshalJSON sets Tag to a copy of data.
func (t *Tag) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if Tag does not comply with the OpenAPI spec.
type Tags []*Tag
Tags is specified by OpenAPI/Swagger 3.0 standard.
func (tags Tags) Get(name string) *Tag
func (tags Tags) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if Tags does not comply with the OpenAPI spec.
type Types []string
func (pTypes *Types) Includes(typ string) bool
func (types *Types) Is(typ string) bool
func (pTypes *Types) MarshalJSON() ([]byte, error)
func (pTypes *Types) MarshalYAML() (interface{}, error)
func (types *Types) Permits(typ string) bool
func (types *Types) Slice() []string
func (types *Types) UnmarshalJSON(data []byte) error
type ValidationOption func(options *ValidationOptions)
ValidationOption allows the modification of how the OpenAPI document is
validated.
func AllowExtraSiblingFields(fields ...string) ValidationOption
AllowExtraSiblingFields called as AllowExtraSiblingFields("description")
makes Validate not return an error when said field appears next to a $ref.
func DisableExamplesValidation() ValidationOption
DisableExamplesValidation disables all example schema validation.
By default, all schema examples are validated.
func DisableSchemaDefaultsValidation() ValidationOption
DisableSchemaDefaultsValidation disables schemas' default field validation.
By default, schema default values are validated against their schema.
func DisableSchemaFormatValidation() ValidationOption
DisableSchemaFormatValidation does the opposite of
EnableSchemaFormatValidation. By default, schema format validation is
disabled.
func DisableSchemaPatternValidation() ValidationOption
DisableSchemaPatternValidation makes Validate not return an error when
validating patterns that are not supported by the Go regexp engine.
func EnableExamplesValidation() ValidationOption
EnableExamplesValidation does the opposite of DisableExamplesValidation.
By default, all schema examples are validated.
func EnableSchemaDefaultsValidation() ValidationOption
EnableSchemaDefaultsValidation does the opposite of
DisableSchemaDefaultsValidation. By default, schema default values are
validated against their schema.
func EnableSchemaFormatValidation() ValidationOption
EnableSchemaFormatValidation makes Validate not return an error when
validating documents that mention schema formats that are not defined by the
OpenAPIv3 specification. By default, schema format validation is disabled.
func EnableSchemaPatternValidation() ValidationOption
EnableSchemaPatternValidation does the opposite of
DisableSchemaPatternValidation. By default, schema pattern validation is
enabled.
type ValidationOptions struct {
// Has unexported fields.
}
ValidationOptions provides configuration for validating OpenAPI documents.
type XML struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"`
Attribute bool `json:"attribute,omitempty" yaml:"attribute,omitempty"`
Wrapped bool `json:"wrapped,omitempty" yaml:"wrapped,omitempty"`
}
XML is specified by OpenAPI/Swagger standard version 3. See
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#xml-object
func (xml XML) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of XML.
func (xml *XML) UnmarshalJSON(data []byte) error
UnmarshalJSON sets XML to a copy of data.
func (xml *XML) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if XML does not comply with the OpenAPI spec.
kin-openapi-0.124.0/.github/docs/openapi3filter.txt 0000664 0000000 0000000 00000042407 14604223742 0022060 0 ustar 00root root 0000000 0000000 package openapi3filter // import "github.com/getkin/kin-openapi/openapi3filter"
Package openapi3filter validates that requests and inputs request an OpenAPI 3
specification file.
CONSTANTS
const (
// ErrCodeOK indicates no error. It is also the default value.
ErrCodeOK = 0
// ErrCodeCannotFindRoute happens when the validator fails to resolve the
// request to a defined OpenAPI route.
ErrCodeCannotFindRoute = iota
// ErrCodeRequestInvalid happens when the inbound request does not conform
// to the OpenAPI 3 specification.
ErrCodeRequestInvalid = iota
// ErrCodeResponseInvalid happens when the wrapped handler response does
// not conform to the OpenAPI 3 specification.
ErrCodeResponseInvalid = iota
)
VARIABLES
var ErrAuthenticationServiceMissing = errors.New("missing AuthenticationFunc")
ErrAuthenticationServiceMissing is returned when no authentication service
is defined for the request validator
var ErrInvalidEmptyValue = errors.New("empty value is not allowed")
ErrInvalidEmptyValue is returned when a value of a parameter or request body
is empty while it's not allowed.
var ErrInvalidRequired = errors.New("value is required but missing")
ErrInvalidRequired is returned when a required value of a parameter or
request body is not defined.
var JSONPrefixes = []string{
")]}',\n",
}
FUNCTIONS
func ConvertErrors(err error) error
ConvertErrors converts all errors to the appropriate error format.
func DefaultErrorEncoder(_ context.Context, err error, w http.ResponseWriter)
DefaultErrorEncoder writes the error to the ResponseWriter, by default a
content type of text/plain, a body of the plain text of the error, and a
status code of 500. If the error implements Headerer, the provided headers
will be applied to the response. If the error implements json.Marshaler,
and the marshaling succeeds, a content type of application/json and the JSON
encoded form of the error will be used. If the error implements StatusCoder,
the provided StatusCode will be used instead of 500.
func FileBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error)
FileBodyDecoder is a body decoder that decodes a file body to a string.
func JSONBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error)
JSONBodyDecoder decodes a JSON formatted body. It is public so that is easy
to register additional JSON based formats.
func NoopAuthenticationFunc(context.Context, *AuthenticationInput) error
NoopAuthenticationFunc is an AuthenticationFunc
func RegisterBodyDecoder(contentType string, decoder BodyDecoder)
RegisterBodyDecoder registers a request body's decoder for a content type.
If a decoder for the specified content type already exists, the function
replaces it with the specified decoder. This call is not thread-safe:
body decoders should not be created/destroyed by multiple goroutines.
func RegisterBodyEncoder(contentType string, encoder BodyEncoder)
RegisterBodyEncoder enables package-wide decoding of contentType values
func TrimJSONPrefix(data []byte) []byte
TrimJSONPrefix trims one of the possible prefixes
func UnregisterBodyDecoder(contentType string)
UnregisterBodyDecoder dissociates a body decoder from a content type.
Decoding this content type will result in an error. This call is not
thread-safe: body decoders should not be created/destroyed by multiple
goroutines.
func UnregisterBodyEncoder(contentType string)
UnregisterBodyEncoder disables package-wide decoding of contentType values
func ValidateParameter(ctx context.Context, input *RequestValidationInput, parameter *openapi3.Parameter) error
ValidateParameter validates a parameter's value by JSON schema. The function
returns RequestError with a ParseError cause when unable to parse a value.
The function returns RequestError with ErrInvalidRequired cause when a value
of a required parameter is not defined. The function returns RequestError
with ErrInvalidEmptyValue cause when a value of a required parameter is not
defined. The function returns RequestError with a openapi3.SchemaError cause
when a value is invalid by JSON schema.
func ValidateRequest(ctx context.Context, input *RequestValidationInput) (err error)
ValidateRequest is used to validate the given input according to previous
loaded OpenAPIv3 spec. If the input does not match the OpenAPIv3 spec,
a non-nil error will be returned.
Note: One can tune the behavior of uniqueItems: true verification by
registering a custom function with openapi3.RegisterArrayUniqueItemsChecker
func ValidateRequestBody(ctx context.Context, input *RequestValidationInput, requestBody *openapi3.RequestBody) error
ValidateRequestBody validates data of a request's body.
The function returns RequestError with ErrInvalidRequired cause when a
value is required but not defined. The function returns RequestError with a
openapi3.SchemaError cause when a value is invalid by JSON schema.
func ValidateResponse(ctx context.Context, input *ResponseValidationInput) error
ValidateResponse is used to validate the given input according to previous
loaded OpenAPIv3 spec. If the input does not match the OpenAPIv3 spec,
a non-nil error will be returned.
Note: One can tune the behavior of uniqueItems: true verification by
registering a custom function with openapi3.RegisterArrayUniqueItemsChecker
func ValidateSecurityRequirements(ctx context.Context, input *RequestValidationInput, srs openapi3.SecurityRequirements) error
ValidateSecurityRequirements goes through multiple OpenAPI 3 security
requirements in order and returns nil on the first valid requirement.
If no requirement is met, errors are returned in order.
TYPES
type AuthenticationFunc func(context.Context, *AuthenticationInput) error
AuthenticationFunc allows for custom security requirement
validation. A non-nil error fails authentication according to
https://spec.openapis.org/oas/v3.1.0#security-requirement-object See
ValidateSecurityRequirements
type AuthenticationInput struct {
RequestValidationInput *RequestValidationInput
SecuritySchemeName string
SecurityScheme *openapi3.SecurityScheme
Scopes []string
}
func (input *AuthenticationInput) NewError(err error) error
type BodyDecoder func(io.Reader, http.Header, *openapi3.SchemaRef, EncodingFn) (interface{}, error)
BodyDecoder is an interface to decode a body of a request or response.
An implementation must return a value that is a primitive, []interface{},
or map[string]interface{}.
func RegisteredBodyDecoder(contentType string) BodyDecoder
RegisteredBodyDecoder returns the registered body decoder for the given
content type.
If no decoder was registered for the given content type, nil is returned.
This call is not thread-safe: body decoders should not be created/destroyed
by multiple goroutines.
type BodyEncoder func(body interface{}) ([]byte, error)
BodyEncoder really is an (encoding/json).Marshaler
func RegisteredBodyEncoder(contentType string) BodyEncoder
RegisteredBodyEncoder returns the registered body encoder for the given
content type.
If no encoder was registered for the given content type, nil is returned.
type ContentParameterDecoder func(param *openapi3.Parameter, values []string) (interface{}, *openapi3.Schema, error)
A ContentParameterDecoder takes a parameter definition from the OpenAPI
spec, and the value which we received for it. It is expected to return the
value unmarshaled into an interface which can be traversed for validation,
it should also return the schema to be used for validating the object,
since there can be more than one in the content spec.
If a query parameter appears multiple times, values[] will have more than
one value, but for all other parameter types it should have just one.
type CustomSchemaErrorFunc func(err *openapi3.SchemaError) string
CustomSchemaErrorFunc allows for custom the schema error message.
type EncodingFn func(partName string) *openapi3.Encoding
EncodingFn is a function that returns an encoding of a request body's part.
type ErrCode int
ErrCode is used for classification of different types of errors that may
occur during validation. These may be used to write an appropriate response
in ErrFunc.
type ErrFunc func(w http.ResponseWriter, status int, code ErrCode, err error)
ErrFunc handles errors that may occur during validation.
type ErrorEncoder func(ctx context.Context, err error, w http.ResponseWriter)
ErrorEncoder is responsible for encoding an error to the ResponseWriter.
Users are encouraged to use custom ErrorEncoders to encode HTTP errors to
their clients, and will likely want to pass and check for their own error
types. See the example shipping/handling service.
type Headerer interface {
Headers() http.Header
}
Headerer is checked by DefaultErrorEncoder. If an error value implements
Headerer, the provided headers will be applied to the response writer,
after the Content-Type is set.
type LogFunc func(message string, err error)
LogFunc handles log messages that may occur during validation.
type Options struct {
// Set ExcludeRequestBody so ValidateRequest skips request body validation
ExcludeRequestBody bool
// Set ExcludeRequestQueryParams so ValidateRequest skips request query params validation
ExcludeRequestQueryParams bool
// Set ExcludeResponseBody so ValidateResponse skips response body validation
ExcludeResponseBody bool
// Set ExcludeReadOnlyValidations so ValidateRequest skips read-only validations
ExcludeReadOnlyValidations bool
// Set ExcludeWriteOnlyValidations so ValidateResponse skips write-only validations
ExcludeWriteOnlyValidations bool
// Set IncludeResponseStatus so ValidateResponse fails on response
// status not defined in OpenAPI spec
IncludeResponseStatus bool
MultiError bool
// A document with security schemes defined will not pass validation
// unless an AuthenticationFunc is defined.
// See NoopAuthenticationFunc
AuthenticationFunc AuthenticationFunc
// Indicates whether default values are set in the
// request. If true, then they are not set
SkipSettingDefaults bool
// Has unexported fields.
}
Options used by ValidateRequest and ValidateResponse
func (o *Options) WithCustomSchemaErrorFunc(f CustomSchemaErrorFunc)
WithCustomSchemaErrorFunc sets a function to override the schema error
message. If the passed function returns an empty string, it returns to the
previous Error() implementation.
type ParseError struct {
Kind ParseErrorKind
Value interface{}
Reason string
Cause error
// Has unexported fields.
}
ParseError describes errors which happens while parse operation's
parameters, requestBody, or response.
func (e *ParseError) Error() string
func (e *ParseError) Path() []interface{}
Path returns a path to the root cause.
func (e *ParseError) RootCause() error
RootCause returns a root cause of ParseError.
func (e ParseError) Unwrap() error
type ParseErrorKind int
ParseErrorKind describes a kind of ParseError. The type simplifies
comparison of errors.
const (
// KindOther describes an untyped parsing error.
KindOther ParseErrorKind = iota
// KindUnsupportedFormat describes an error that happens when a value has an unsupported format.
KindUnsupportedFormat
// KindInvalidFormat describes an error that happens when a value does not conform a format
// that is required by a serialization method.
KindInvalidFormat
)
type RequestError struct {
Input *RequestValidationInput
Parameter *openapi3.Parameter
RequestBody *openapi3.RequestBody
Reason string
Err error
}
RequestError is returned by ValidateRequest when request does not match
OpenAPI spec
func (err *RequestError) Error() string
func (err RequestError) Unwrap() error
type RequestValidationInput struct {
Request *http.Request
PathParams map[string]string
QueryParams url.Values
Route *routers.Route
Options *Options
ParamDecoder ContentParameterDecoder
}
func (input *RequestValidationInput) GetQueryParams() url.Values
type ResponseError struct {
Input *ResponseValidationInput
Reason string
Err error
}
ResponseError is returned by ValidateResponse when response does not match
OpenAPI spec
func (err *ResponseError) Error() string
func (err ResponseError) Unwrap() error
type ResponseValidationInput struct {
RequestValidationInput *RequestValidationInput
Status int
Header http.Header
Body io.ReadCloser
Options *Options
}
func (input *ResponseValidationInput) SetBodyBytes(value []byte) *ResponseValidationInput
type SecurityRequirementsError struct {
SecurityRequirements openapi3.SecurityRequirements
Errors []error
}
SecurityRequirementsError is returned by ValidateSecurityRequirements when
no requirement is met.
func (err *SecurityRequirementsError) Error() string
func (err SecurityRequirementsError) Unwrap() []error
type StatusCoder interface {
StatusCode() int
}
StatusCoder is checked by DefaultErrorEncoder. If an error value implements
StatusCoder, the StatusCode will be used when encoding the error.
By default, StatusInternalServerError (500) is used.
type ValidationError struct {
// A unique identifier for this particular occurrence of the problem.
Id string `json:"id,omitempty" yaml:"id,omitempty"`
// The HTTP status code applicable to this problem.
Status int `json:"status,omitempty" yaml:"status,omitempty"`
// An application-specific error code, expressed as a string value.
Code string `json:"code,omitempty" yaml:"code,omitempty"`
// A short, human-readable summary of the problem. It **SHOULD NOT** change from occurrence to occurrence of the problem, except for purposes of localization.
Title string `json:"title,omitempty" yaml:"title,omitempty"`
// A human-readable explanation specific to this occurrence of the problem.
Detail string `json:"detail,omitempty" yaml:"detail,omitempty"`
// An object containing references to the source of the error
Source *ValidationErrorSource `json:"source,omitempty" yaml:"source,omitempty"`
}
ValidationError struct provides granular error information useful
for communicating issues back to end user and developer. Based on
https://jsonapi.org/format/#error-objects
func (e *ValidationError) Error() string
Error implements the error interface.
func (e *ValidationError) StatusCode() int
StatusCode implements the StatusCoder interface for DefaultErrorEncoder
type ValidationErrorEncoder struct {
Encoder ErrorEncoder
}
ValidationErrorEncoder wraps a base ErrorEncoder to handle ValidationErrors
func (enc *ValidationErrorEncoder) Encode(ctx context.Context, err error, w http.ResponseWriter)
Encode implements the ErrorEncoder interface for encoding ValidationErrors
type ValidationErrorSource struct {
// A JSON Pointer [RFC6901] to the associated entity in the request document [e.g. \"/data\" for a primary data object, or \"/data/attributes/title\" for a specific attribute].
Pointer string `json:"pointer,omitempty" yaml:"pointer,omitempty"`
// A string indicating which query parameter caused the error.
Parameter string `json:"parameter,omitempty" yaml:"parameter,omitempty"`
}
ValidationErrorSource struct
type ValidationHandler struct {
Handler http.Handler
AuthenticationFunc AuthenticationFunc
File string
ErrorEncoder ErrorEncoder
// Has unexported fields.
}
func (h *ValidationHandler) Load() error
func (h *ValidationHandler) Middleware(next http.Handler) http.Handler
Middleware implements gorilla/mux MiddlewareFunc
func (h *ValidationHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
type Validator struct {
// Has unexported fields.
}
Validator provides HTTP request and response validation middleware.
func NewValidator(router routers.Router, options ...ValidatorOption) *Validator
NewValidator returns a new response validation middleware, using the given
routes from an OpenAPI 3 specification.
func (v *Validator) Middleware(h http.Handler) http.Handler
Middleware returns an http.Handler which wraps the given handler with
request and response validation.
type ValidatorOption func(*Validator)
ValidatorOption defines an option that may be specified when creating a
Validator.
func OnErr(f ErrFunc) ValidatorOption
OnErr provides a callback that handles writing an HTTP response on a
validation error. This allows customization of error responses without
prescribing a particular form. This callback is only called on response
validator errors in Strict mode.
func OnLog(f LogFunc) ValidatorOption
OnLog provides a callback that handles logging in the Validator. This allows
the validator to integrate with a services' existing logging system without
prescribing a particular one.
func Strict(strict bool) ValidatorOption
Strict, if set, causes an internal server error to be sent if the wrapped
handler response fails response validation. If not set, the response is sent
and the error is only logged.
func ValidationOptions(options Options) ValidatorOption
ValidationOptions sets request/response validation options on the validator.
kin-openapi-0.124.0/.github/docs/openapi3gen.txt 0000664 0000000 0000000 00000006617 14604223742 0021347 0 ustar 00root root 0000000 0000000 package openapi3gen // import "github.com/getkin/kin-openapi/openapi3gen"
Package openapi3gen generates OpenAPIv3 JSON schemas from Go types.
VARIABLES
var RefSchemaRef = openapi3.NewSchemaRef("Ref",
openapi3.NewObjectSchema().WithProperty("$ref", openapi3.NewStringSchema().WithMinLength(1)))
FUNCTIONS
func NewSchemaRefForValue(value interface{}, schemas openapi3.Schemas, opts ...Option) (*openapi3.SchemaRef, error)
NewSchemaRefForValue is a shortcut for
NewGenerator(...).NewSchemaRefForValue(...)
TYPES
type CycleError struct{}
CycleError indicates that a type graph has one or more possible cycles.
func (err *CycleError) Error() string
type ExcludeSchemaSentinel struct{}
ExcludeSchemaSentinel indicates that the schema for a specific field should
not be included in the final output.
func (err *ExcludeSchemaSentinel) Error() string
type ExportComponentSchemasOptions struct {
ExportComponentSchemas bool
ExportTopLevelSchema bool
ExportGenerics bool
}
type Generator struct {
Types map[reflect.Type]*openapi3.SchemaRef
// SchemaRefs contains all references and their counts.
// If count is 1, it's not ne
// An OpenAPI identifier has been assigned to each.
SchemaRefs map[*openapi3.SchemaRef]int
// Has unexported fields.
}
func NewGenerator(opts ...Option) *Generator
func (g *Generator) GenerateSchemaRef(t reflect.Type) (*openapi3.SchemaRef, error)
func (g *Generator) NewSchemaRefForValue(value interface{}, schemas openapi3.Schemas) (*openapi3.SchemaRef, error)
NewSchemaRefForValue uses reflection on the given value to produce a
SchemaRef, and updates a supplied map with any dependent component schemas
if they lead to cycles
type Option func(*generatorOpt)
Option allows tweaking SchemaRef generation
func CreateComponentSchemas(exso ExportComponentSchemasOptions) Option
CreateComponents changes the default behavior to add all schemas as
components Reduces duplicate schemas in routes
func CreateTypeNameGenerator(tngnrt TypeNameGenerator) Option
func SchemaCustomizer(sc SchemaCustomizerFn) Option
SchemaCustomizer allows customization of the schema that is generated for a
field, for example to support an additional tagging scheme
func ThrowErrorOnCycle() Option
ThrowErrorOnCycle changes the default behavior of creating cycle refs to
instead error if a cycle is detected.
func UseAllExportedFields() Option
UseAllExportedFields changes the default behavior of only generating schemas
for struct fields with a JSON tag.
type SchemaCustomizerFn func(name string, t reflect.Type, tag reflect.StructTag, schema *openapi3.Schema) error
SchemaCustomizerFn is a callback function, allowing the OpenAPI schema
definition to be updated with additional properties during the generation
process, based on the name of the field, the Go type, and the struct tags.
name will be "_root" for the top level object, and tag will be "".
A SchemaCustomizerFn can return an ExcludeSchemaSentinel error to indicate
that the schema for this field should not be included in the final output
type SetSchemar interface {
SetSchema(*openapi3.Schema)
}
SetSchemar allows client to set their own schema definition according to
their specification. Useful when some custom datatype is needed and/or some
custom logic is needed on how the schema values would be generated
type TypeNameGenerator func(t reflect.Type) string
kin-openapi-0.124.0/.github/docs/routers.txt 0000664 0000000 0000000 00000002403 14604223742 0020627 0 ustar 00root root 0000000 0000000 package routers // import "github.com/getkin/kin-openapi/routers"
VARIABLES
var ErrMethodNotAllowed error = &RouteError{"method not allowed"}
ErrMethodNotAllowed is returned when no method of the matched route matches
var ErrPathNotFound error = &RouteError{"no matching operation was found"}
ErrPathNotFound is returned when no route match is found
TYPES
type Route struct {
Spec *openapi3.T
Server *openapi3.Server
Path string
PathItem *openapi3.PathItem
Method string
Operation *openapi3.Operation
}
Route describes the operation an http.Request can match
type RouteError struct {
Reason string
}
RouteError describes Router errors
func (e *RouteError) Error() string
type Router interface {
// FindRoute matches an HTTP request with the operation it resolves to.
// Hosts are matched from the OpenAPIv3 servers key.
//
// If you experience ErrPathNotFound and have localhost hosts specified as your servers,
// turning these server URLs as relative (leaving only the path) should resolve this.
//
// See openapi3filter for example uses with request and response validation.
FindRoute(req *http.Request) (route *Route, pathParams map[string]string, err error)
}
Router helps link http.Request.s and an OpenAPIv3 spec
kin-openapi-0.124.0/.github/docs/routers_gorillamux.txt 0000664 0000000 0000000 00000001654 14604223742 0023101 0 ustar 00root root 0000000 0000000 package gorillamux // import "github.com/getkin/kin-openapi/routers/gorillamux"
Package gorillamux implements a router.
It differs from the legacy router: * it provides somewhat granular errors: "path
not found", "method not allowed". * it handles matching routes with extensions
(e.g. /books/{id}.json) * it handles path patterns with a different syntax (e.g.
/params/{x}/{y}/{z:.*})
FUNCTIONS
func NewRouter(doc *openapi3.T) (routers.Router, error)
NewRouter creates a gorilla/mux router. Assumes spec is .Validate()d Note
that a variable for the port number MUST have a default value and only this
value will match as the port (see issue #367).
TYPES
type Router struct {
// Has unexported fields.
}
Router helps link http.Request.s and an OpenAPIv3 spec
func (r *Router) FindRoute(req *http.Request) (*routers.Route, map[string]string, error)
FindRoute extracts the route and parameters of an http.Request
kin-openapi-0.124.0/.github/docs/routers_legacy.txt 0000664 0000000 0000000 00000002430 14604223742 0022153 0 ustar 00root root 0000000 0000000 package legacy // import "github.com/getkin/kin-openapi/routers/legacy"
Package legacy implements a router.
It differs from the gorilla/mux router: * it provides granular errors: "path
not found", "method not allowed", "variable missing from path" * it does not
handle matching routes with extensions (e.g. /books/{id}.json) * it handles path
patterns with a different syntax (e.g. /params/{x}/{y}/{z.*})
FUNCTIONS
func NewRouter(doc *openapi3.T, opts ...openapi3.ValidationOption) (routers.Router, error)
NewRouter creates a new router.
If the given OpenAPIv3 document has servers, router will use them. All
operations of the document will be added to the router.
TYPES
type Router struct {
// Has unexported fields.
}
Router maps a HTTP request to an OpenAPI operation.
func (router *Router) AddRoute(route *routers.Route) error
AddRoute adds a route in the router.
func (router *Router) FindRoute(req *http.Request) (*routers.Route, map[string]string, error)
FindRoute extracts the route and parameters of an http.Request
type Routers []*Router
Routers maps a HTTP request to a Router.
func (rs Routers) FindRoute(req *http.Request) (routers.Router, *routers.Route, map[string]string, error)
FindRoute extracts the route and parameters of an http.Request
kin-openapi-0.124.0/.github/docs/routers_legacy_pathpattern.txt 0000664 0000000 0000000 00000004112 14604223742 0024564 0 ustar 00root root 0000000 0000000 package pathpattern // import "github.com/getkin/kin-openapi/routers/legacy/pathpattern"
Package pathpattern implements path matching.
Examples of supported patterns:
- "/"
- "/abc""
- "/abc/{variable}" (matches until next '/' or end-of-string)
- "/abc/{variable*}" (matches everything, including "/abc" if "/abc" has root)
- "/abc/{ variable | prefix_(.*}_suffix }" (matches regular expressions)
CONSTANTS
const (
// SuffixKindConstant matches a constant string
SuffixKindConstant = SuffixKind(iota)
// SuffixKindRegExp matches a regular expression
SuffixKindRegExp
// SuffixKindVariable matches everything until '/'
SuffixKindVariable
// SuffixKindEverything matches everything (until end-of-string)
SuffixKindEverything
)
Note that order is important!
VARIABLES
var DefaultOptions = &Options{
SupportWildcard: true,
}
FUNCTIONS
func EqualSuffix(a, b Suffix) bool
func PathFromHost(host string, specialDashes bool) string
PathFromHost converts a host pattern to a path pattern.
Examples:
- PathFromHost("some-subdomain.domain.com", false) ->
"com/./domain/./some-subdomain"
- PathFromHost("some-subdomain.domain.com", true) ->
"com/./domain/./subdomain/-/some"
TYPES
type Node struct {
VariableNames []string
Value interface{}
Suffixes SuffixList
}
func (currentNode *Node) Add(path string, value interface{}, options *Options) error
func (currentNode *Node) CreateNode(path string, options *Options) (*Node, error)
func (currentNode *Node) Match(path string) (*Node, []string)
func (currentNode *Node) MustAdd(path string, value interface{}, options *Options)
func (currentNode *Node) String() string
type Options struct {
SupportWildcard bool
SupportRegExp bool
}
type Suffix struct {
Kind SuffixKind
Pattern string
// Next node
Node *Node
// Has unexported fields.
}
Suffix describes condition that
func (suffix Suffix) String() string
type SuffixKind int
type SuffixList []Suffix
func (list SuffixList) Len() int
func (list SuffixList) Less(i, j int) bool
func (list SuffixList) Swap(i, j int)
kin-openapi-0.124.0/.github/sponsors/ 0000775 0000000 0000000 00000000000 14604223742 0017322 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/.github/sponsors/speakeasy-github-sponsor-dark.svg 0000664 0000000 0000000 00000046071 14604223742 0025740 0 ustar 00root root 0000000 0000000
kin-openapi-0.124.0/.github/sponsors/speakeasy-github-sponsor-light.svg 0000664 0000000 0000000 00000046067 14604223742 0026133 0 ustar 00root root 0000000 0000000
kin-openapi-0.124.0/.github/workflows/ 0000775 0000000 0000000 00000000000 14604223742 0017471 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/.github/workflows/codeql.yml 0000664 0000000 0000000 00000001500 14604223742 0021457 0 ustar 00root root 0000000 0000000 name: "CodeQL"
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
schedule:
- cron: "4 8 * * 4"
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ go ]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
queries: +security-and-quality
- name: Autobuild
uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{ matrix.language }}"
kin-openapi-0.124.0/.github/workflows/go.yml 0000664 0000000 0000000 00000014704 14604223742 0020627 0 ustar 00root root 0000000 0000000 name: go
on:
pull_request:
push:
jobs:
build-and-test:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GO111MODULE: 'on'
CGO_ENABLED: '0'
strategy:
fail-fast: true
matrix:
go: ['1.x']
os:
- ubuntu-latest
- windows-latest
- macos-latest
runs-on: ${{ matrix.os }}
defaults:
run:
shell: bash
name: ${{ matrix.go }} on ${{ matrix.os }}
steps:
- uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go }}
- id: go-cache-paths
run: |
echo "::set-output name=go-build::$(go env GOCACHE)"
echo "::set-output name=go-mod::$(go env GOMODCACHE)"
- run: echo ${{ steps.go-cache-paths.outputs.go-build }}
- run: echo ${{ steps.go-cache-paths.outputs.go-mod }}
- name: Go Build Cache
uses: actions/cache@v3
with:
path: ${{ steps.go-cache-paths.outputs.go-build }}
key: ${{ runner.os }}-go-${{ matrix.go }}-build-${{ hashFiles('**/go.sum') }}
- name: Go Mod Cache (go>=1.15)
uses: actions/cache@v3
with:
path: ${{ steps.go-cache-paths.outputs.go-mod }}
key: ${{ runner.os }}-go-${{ matrix.go }}-mod-${{ hashFiles('**/go.sum') }}
- if: runner.os == 'Linux'
run: sudo apt install silversearcher-ag
- uses: actions/checkout@v2
- run: ./refs.sh | tee openapi3/refs.go
- run: git --no-pager diff --exit-code
- run: ./maps.sh
- run: git --no-pager diff --exit-code
- run: ./docs.sh
- run: go mod download && go mod tidy && go mod verify
- run: git --no-pager diff --exit-code
- run: go vet ./...
- run: git --no-pager diff --exit-code
- run: go fmt ./...
- run: git --no-pager diff --exit-code
- run: go test ./...
- if: runner.os == 'Linux'
run: go test -count=10 ./...
env:
GOARCH: '386'
- run: go test -count=10 ./...
- run: go test -count=2 -covermode=atomic ./...
- run: go test -v -count=10 -run TestRaceyPatternSchemaValidateHindersIt -race ./...
env:
CGO_ENABLED: '1'
- run: go test -v -count=10 -run TestRaceyPatternSchemaForIssue775 -race ./...
env:
CGO_ENABLED: '1'
- run: go test -v -count=10 -run TestIssue741 -race ./...
env:
CGO_ENABLED: '1'
- run: git --no-pager diff --exit-code
- if: runner.os == 'Linux'
name: Errors must not be capitalized https://github.com/golang/go/wiki/CodeReviewComments#error-strings
run: |
! git grep -E '(fmt|errors)[^(]+\(.[A-Z]'
- if: runner.os == 'Linux'
name: Did you mean %q
run: |
! git grep -E "'[%].'"
- if: runner.os == 'Linux'
name: Also add yaml tags
run: |
! git grep -InE 'json:"' | grep -v _test.go | grep -v yaml:
- if: runner.os == 'Linux'
name: nilness
run: go run golang.org/x/tools/go/analysis/passes/nilness/cmd/nilness@latest ./...
- if: runner.os == 'Linux'
name: Check for superfluous trailing whitespace
run: |
! grep -IErn '\s$' --exclude-dir={.git,target,pgdata}
- if: runner.os == 'Linux'
name: Ensure use of unmarshal
run: |
[[ "$(git grep -F yaml. -- openapi3/ | grep -v _test.go | wc -l)" = 1 ]]
- if: runner.os == 'Linux'
name: Use `loader := NewLoader(); loader.Load ...`
run: |
! git grep -InE 'NewLoader[(][)][.]Load'
- if: runner.os == 'Linux'
name: Use raw quotes https://go.dev/ref/spec#String_literals
run: |
! git grep -InE 'require[.].+\\"'
- if: runner.os == 'Linux'
name: Use require.ErrorContains(..)
run: |
! git grep -InE 'require[.].+err.Error'
- if: runner.os == 'Linux'
name: Ensure go-run'ing binaries works
run: go run -a github.com/getkin/kin-openapi/cmd/validate@latest -- openapi3/testdata/test.openapi.yml
- if: runner.os == 'Linux'
name: Missing specification object link to definition
run: |
[[ 31 -eq $(git grep -InE '^// See https:.+OpenAPI-Specification.+3[.]0[.]3[.]md#.+bject$' openapi3/*.go | grep -v _test.go | grep -v doc.go | wc -l) ]]
- if: runner.os == 'Linux'
name: Missing validation of unknown fields in extensions
run: |
[[ $(git grep -InE 'return .*validateExtensions' -- openapi3 | wc -l) -eq $(git grep -InE '^\s+Extensions.+`' -- openapi3 | wc -l) ]]
- if: runner.os == 'Linux'
name: Style around Extensions embedding
run: |
! ag -B2 -A2 'type.[A-Z].+struct..\n.+Extensions\n[^\n]' openapi3/*.go
- if: runner.os == 'Linux'
name: Ensure all exported fields are mentioned in Validate() impls
run: |
for ty in $TYPES; do
# Ensure definition
if ! ag 'type.[A-Z].+struct..\n.+Extensions' openapi3/*.go | grep -F "type $ty struct"; then
echo "OAI type $ty is not defined" && exit 1
fi
# Ensure impl Validate()
if ! git grep -InE 'func [(].+'"$ty"'[)] Validate[(]ctx context.Context, opts [.][.][.]ValidationOption[)].+error.+[{]'; then
echo "OAI type $ty does not implement Validate()" && exit 1
fi
# TODO: $ty mention all its exported fields within Validate()
done
env:
TYPES: >
Components
Contact
Discriminator
Encoding
Example
ExternalDocs
Info
License
Link
MediaType
OAuthFlow
OAuthFlows
Operation
Parameter
PathItem
RequestBody
Response
Schema
SecurityScheme
Server
ServerVariable
T
Tag
XML
- if: runner.os == 'Linux'
name: Ensure readableType() covers all possible values of resolved var
run: |
[[ "$(git grep -F 'var resolved ' -- openapi3/loader.go | awk '{print $4}' | sort | tr '\n' ' ')" = "$RESOLVEDS" ]]
env:
RESOLVEDS: 'Callback CallbackRef ExampleRef HeaderRef LinkRef ParameterRef PathItem RequestBodyRef ResponseRef SchemaRef SecuritySchemeRef '
check-goimports:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v3
with:
go-version: '>=1.17.0'
- run: go install github.com/incu6us/goimports-reviser/v2@latest
- run: which goimports-reviser
- run: find . -type f -iname '*.go' ! -iname '*.pb.go' -exec goimports-reviser -file-path {} \;
- run: git --no-pager diff --exit-code
kin-openapi-0.124.0/.github/workflows/shellcheck.yml 0000664 0000000 0000000 00000000450 14604223742 0022320 0 ustar 00root root 0000000 0000000 name: ShellCheck
on:
push:
pull_request:
jobs:
shellcheck:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Run shellcheck
uses: ludeeus/action-shellcheck@1.1.0
with:
check_together: 'yes'
severity: error
kin-openapi-0.124.0/.gitignore 0000664 0000000 0000000 00000000527 14604223742 0016070 0 ustar 00root root 0000000 0000000 # Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/
# Macos file system
.DS_Store
# IntelliJ / GoLand
.idea
.vscode
kin-openapi-0.124.0/LICENSE 0000664 0000000 0000000 00000002072 14604223742 0015102 0 ustar 00root root 0000000 0000000 MIT License
Copyright (c) 2017-2018 the project authors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
kin-openapi-0.124.0/README.md 0000664 0000000 0000000 00000037643 14604223742 0015370 0 ustar 00root root 0000000 0000000 [](https://github.com/getkin/kin-openapi/actions)
[](https://goreportcard.com/report/github.com/getkin/kin-openapi)
[](https://godoc.org/github.com/getkin/kin-openapi)
[](https://gitter.im/getkin/kin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
# Introduction
A [Go](https://golang.org) project for handling [OpenAPI](https://www.openapis.org/) files. We target:
* [OpenAPI `v2.0`](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md) (formerly known as Swagger)
* [OpenAPI `v3.0`](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md)
* [OpenAPI `v3.1`](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md) Soon! [Tracking issue here.](https://github.com/getkin/kin-openapi/issues/230)
Licensed under the [MIT License](./LICENSE).
## Contributors, users and sponsors
The project has received pull requests [from many people](https://github.com/getkin/kin-openapi/graphs/contributors). Thanks to everyone!
Be sure to [give back to this project](https://github.com/sponsors/fenollp) like our sponsors:
Here's some projects that depend on _kin-openapi_:
* [github.com/Tufin/oasdiff](https://github.com/Tufin/oasdiff) - "A diff tool for OpenAPI Specification 3"
* [github.com/danielgtaylor/apisprout](https://github.com/danielgtaylor/apisprout) - "Lightweight, blazing fast, cross-platform OpenAPI 3 mock server with validation"
* [github.com/deepmap/oapi-codegen](https://github.com/deepmap/oapi-codegen) - "Generate Go client and server boilerplate from OpenAPI 3 specifications"
* [github.com/dunglas/vulcain](https://github.com/dunglas/vulcain) - "Use HTTP/2 Server Push to create fast and idiomatic client-driven REST APIs"
* [github.com/danielgtaylor/restish](https://github.com/danielgtaylor/restish) - "...a CLI for interacting with REST-ish HTTP APIs with some nice features built-in"
* [github.com/goadesign/goa](https://github.com/goadesign/goa) - "Design-based APIs and microservices in Go"
* [github.com/hashicorp/nomad-openapi](https://github.com/hashicorp/nomad-openapi) - "Nomad is an easy-to-use, flexible, and performant workload orchestrator that can deploy a mix of microservice, batch, containerized, and non-containerized applications. Nomad is easy to operate and scale and has native Consul and Vault integrations."
* [gitlab.com/jamietanna/httptest-openapi](https://gitlab.com/jamietanna/httptest-openapi) ([*blog post*](https://www.jvt.me/posts/2022/05/22/go-openapi-contract-test/)) - "Go OpenAPI Contract Verification for use with `net/http`"
* [github.com/SIMITGROUP/openapigenerator](https://github.com/SIMITGROUP/openapigenerator) - "Openapi v3 microservices generator"
* [https://github.com/projectsveltos/addon-controller](https://github.com/projectsveltos/addon-controller) - "Kubernetes add-on controller designed to manage tens of clusters."
* (Feel free to add your project by [creating an issue](https://github.com/getkin/kin-openapi/issues/new) or a pull request)
## Alternatives
* [go-swagger](https://github.com/go-swagger/go-swagger) stated [*OpenAPIv3 won't be supported*](https://github.com/go-swagger/go-swagger/issues/1122#issuecomment-575968499)
* [swaggo](https://github.com/swaggo/swag) has an [open issue on OpenAPIv3](https://github.com/swaggo/swag/issues/386)
* [go-openapi](https://github.com/go-openapi)'s [spec3](https://github.com/go-openapi/spec3)
* an iteration on [spec](https://github.com/go-openapi/spec) (for OpenAPIv2)
* see [README](https://github.com/go-openapi/spec3/tree/3fab9faa9094e06ebd19ded7ea96d156c2283dca#oai-object-model---) for the missing parts
Be sure to check [OpenAPI Initiative](https://github.com/OAI)'s [great tooling list](https://github.com/OAI/OpenAPI-Specification/blob/master/IMPLEMENTATIONS.md) as well as [OpenAPI.Tools](https://openapi.tools/).
# Structure
* _openapi2_ ([godoc](https://godoc.org/github.com/getkin/kin-openapi/openapi2))
* Support for OpenAPI 2 files, including serialization, deserialization, and validation.
* _openapi2conv_ ([godoc](https://godoc.org/github.com/getkin/kin-openapi/openapi2conv))
* Converts OpenAPI 2 files into OpenAPI 3 files.
* _openapi3_ ([godoc](https://godoc.org/github.com/getkin/kin-openapi/openapi3))
* Support for OpenAPI 3 files, including serialization, deserialization, and validation.
* _openapi3filter_ ([godoc](https://godoc.org/github.com/getkin/kin-openapi/openapi3filter))
* Validates HTTP requests and responses
* Provides a [gorilla/mux](https://github.com/gorilla/mux) router for OpenAPI operations
* _openapi3gen_ ([godoc](https://godoc.org/github.com/getkin/kin-openapi/openapi3gen))
* Generates `*openapi3.Schema` values for Go types.
# Some recipes
## Validating an OpenAPI document
```shell
go run github.com/getkin/kin-openapi/cmd/validate@latest [--circular] [--defaults] [--examples] [--ext] [--patterns] --
```
## Loading OpenAPI document
Use `openapi3.Loader`, which resolves all references:
```go
loader := openapi3.NewLoader()
doc, err := loader.LoadFromFile("my-openapi-spec.json")
```
## Getting OpenAPI operation that matches request
```go
loader := openapi3.NewLoader()
doc, _ := loader.LoadFromData([]byte(`...`))
_ = doc.Validate(loader.Context)
router, _ := gorillamux.NewRouter(doc)
route, pathParams, _ := router.FindRoute(httpRequest)
// Do something with route.Operation
```
## Validating HTTP requests/responses
```go
package main
import (
"context"
"fmt"
"net/http"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/openapi3filter"
"github.com/getkin/kin-openapi/routers/gorillamux"
)
func main() {
ctx := context.Background()
loader := &openapi3.Loader{Context: ctx, IsExternalRefsAllowed: true}
doc, _ := loader.LoadFromFile(".../My-OpenAPIv3-API.yml")
// Validate document
_ = doc.Validate(ctx)
router, _ := gorillamux.NewRouter(doc)
httpReq, _ := http.NewRequest(http.MethodGet, "/items", nil)
// Find route
route, pathParams, _ := router.FindRoute(httpReq)
// Validate request
requestValidationInput := &openapi3filter.RequestValidationInput{
Request: httpReq,
PathParams: pathParams,
Route: route,
}
_ = openapi3filter.ValidateRequest(ctx, requestValidationInput)
// Handle that request
// --> YOUR CODE GOES HERE <--
responseHeaders := http.Header{"Content-Type": []string{"application/json"}}
responseCode := 200
responseBody := []byte(`{}`)
// Validate response
responseValidationInput := &openapi3filter.ResponseValidationInput{
RequestValidationInput: requestValidationInput,
Status: responseCode,
Header: responseHeaders,
}
responseValidationInput.SetBodyBytes(responseBody)
_ = openapi3filter.ValidateResponse(ctx, responseValidationInput)
}
```
## Custom content type for body of HTTP request/response
By default, the library parses a body of the HTTP request and response
if it has one of the following content types: `"text/plain"` or `"application/json"`.
To support other content types you must register decoders for them:
```go
func main() {
// ...
// Register a body's decoder for content type "application/xml".
openapi3filter.RegisterBodyDecoder("application/xml", xmlBodyDecoder)
// Now you can validate HTTP request that contains a body with content type "application/xml".
requestValidationInput := &openapi3filter.RequestValidationInput{
Request: httpReq,
PathParams: pathParams,
Route: route,
}
if err := openapi3filter.ValidateRequest(ctx, requestValidationInput); err != nil {
panic(err)
}
// ...
// And you can validate HTTP response that contains a body with content type "application/xml".
if err := openapi3filter.ValidateResponse(ctx, responseValidationInput); err != nil {
panic(err)
}
}
func xmlBodyDecoder(body io.Reader, h http.Header, schema *openapi3.SchemaRef, encFn openapi3filter.EncodingFn) (decoded interface{}, err error) {
// Decode body to a primitive, []interface{}, or map[string]interface{}.
}
```
## Custom function to check uniqueness of array items
By default, the library checks unique items using the following predefined function:
```go
func isSliceOfUniqueItems(xs []interface{}) bool {
s := len(xs)
m := make(map[string]struct{}, s)
for _, x := range xs {
key, _ := json.Marshal(&x)
m[string(key)] = struct{}{}
}
return s == len(m)
}
```
In the predefined function `json.Marshal` is used to generate a string that can
be used as a map key which is to check the uniqueness of an array
when the array items are objects or arrays. You can register
you own function according to your input data to get better performance:
```go
func main() {
// ...
// Register a customized function used to check uniqueness of array.
openapi3.RegisterArrayUniqueItemsChecker(arrayUniqueItemsChecker)
// ... other validate codes
}
func arrayUniqueItemsChecker(items []interface{}) bool {
// Check the uniqueness of the input slice
}
```
## Custom function to change schema error messages
By default, the error message returned when validating a value includes the error reason, the schema, and the input value.
For example, given the following schema:
```json
{
"type": "string",
"allOf": [
{ "pattern": "[A-Z]" },
{ "pattern": "[a-z]" },
{ "pattern": "[0-9]" },
{ "pattern": "[!@#$%^&*()_+=-?~]" }
]
}
```
Passing the input value `"secret"` to this schema will produce the following error message:
```
string doesn't match the regular expression "[A-Z]"
Schema:
{
"pattern": "[A-Z]"
}
Value:
"secret"
```
Including the original value in the error message can be helpful for debugging, but it may not be appropriate for sensitive information such as secrets.
To disable the extra details in the schema error message, you can set the `openapi3.SchemaErrorDetailsDisabled` option to `true`:
```go
func main() {
// ...
// Disable schema error detailed error messages
openapi3.SchemaErrorDetailsDisabled = true
// ... other validate codes
}
```
This will shorten the error message to present only the reason:
```
string doesn't match the regular expression "[A-Z]"
```
For more fine-grained control over the error message, you can pass a custom `openapi3filter.Options` object to `openapi3filter.RequestValidationInput` that includes a `openapi3filter.CustomSchemaErrorFunc`.
```go
func validationOptions() *openapi3filter.Options {
options := &openapi3filter.Options{}
options.WithCustomSchemaErrorFunc(safeErrorMessage)
return options
}
func safeErrorMessage(err *openapi3.SchemaError) string {
return err.Reason
}
```
This will change the schema validation errors to return only the `Reason` field, which is guaranteed to not include the original value.
## CHANGELOG: Sub-v1 breaking API changes
### v0.122.0
* `Paths` field of `openapi3.T` is now a pointer
* `Responses` field of `openapi3.Operation` is now a pointer
* `openapi3.Paths` went from `map[string]*PathItem` to a struct with an `Extensions` field and methods: `Set`, `Value`, `Len`, `Map`, and `New*`.
* `openapi3.Callback` went from `map[string]*PathItem` to a struct with an `Extensions` field and methods: `Set`, `Value`, `Len`, `Map`, and `New*`.
* `openapi3.Responses` went from `map[string]*ResponseRef` to a struct with an `Extensions` field and methods: `Set`, `Value`, `Len`, `Map`, and `New*`.
* `(openapi3.Responses).Get(int)` renamed to `(*openapi3.Responses).Status(int)`
### v0.121.0
* Introduce `openapi3.RequestBodies` (an alias on `map[string]*openapi3.ResponseRef`) and use it in place of `openapi3.Responses` for field `openapi3.Components.Responses`.
### v0.116.0
* Dropped `openapi3filter.DefaultOptions`. Use `&openapi3filter.Options{}` directly instead.
### v0.113.0
* The string format `email` has been removed by default. To use it please call `openapi3.DefineStringFormat("email", openapi3.FormatOfStringForEmail)`.
* Field `openapi3.T.Components` is now a pointer.
* Fields `openapi3.Schema.AdditionalProperties` and `openapi3.Schema.AdditionalPropertiesAllowed` are replaced by `openapi3.Schema.AdditionalProperties.Schema` and `openapi3.Schema.AdditionalProperties.Has` respectively.
* Type `openapi3.ExtensionProps` is now just `map[string]interface{}` and extensions are accessible through the `Extensions` field.
### v0.112.0
* `(openapi3.ValidationOptions).ExamplesValidationDisabled` has been unexported.
* `(openapi3.ValidationOptions).SchemaFormatValidationEnabled` has been unexported.
* `(openapi3.ValidationOptions).SchemaPatternValidationDisabled` has been unexported.
### v0.111.0
* Changed `func (*_) Validate(ctx context.Context) error` to `func (*_) Validate(ctx context.Context, opts ...ValidationOption) error`.
* `openapi3.WithValidationOptions(ctx context.Context, opts *ValidationOptions) context.Context` prototype changed to `openapi3.WithValidationOptions(ctx context.Context, opts ...ValidationOption) context.Context`.
### v0.101.0
* `openapi3.SchemaFormatValidationDisabled` has been removed in favour of an option `openapi3.EnableSchemaFormatValidation()` passed to `openapi3.T.Validate`. The default behaviour is also now to not validate formats, as the OpenAPI spec mentions the `format` is an open value.
### v0.84.0
* The prototype of `openapi3gen.NewSchemaRefForValue` changed:
* It no longer returns a map but that is still accessible under the field `(*Generator).SchemaRefs`.
* It now takes in an additional argument (basically `doc.Components.Schemas`) which gets written to so `$ref` cycles can be properly handled.
### v0.61.0
* Renamed `openapi2.Swagger` to `openapi2.T`.
* Renamed `openapi2conv.FromV3Swagger` to `openapi2conv.FromV3`.
* Renamed `openapi2conv.ToV3Swagger` to `openapi2conv.ToV3`.
* Renamed `openapi3.LoadSwaggerFromData` to `openapi3.LoadFromData`.
* Renamed `openapi3.LoadSwaggerFromDataWithPath` to `openapi3.LoadFromDataWithPath`.
* Renamed `openapi3.LoadSwaggerFromFile` to `openapi3.LoadFromFile`.
* Renamed `openapi3.LoadSwaggerFromURI` to `openapi3.LoadFromURI`.
* Renamed `openapi3.NewSwaggerLoader` to `openapi3.NewLoader`.
* Renamed `openapi3.Swagger` to `openapi3.T`.
* Renamed `openapi3.SwaggerLoader` to `openapi3.Loader`.
* Renamed `openapi3filter.ValidationHandler.SwaggerFile` to `openapi3filter.ValidationHandler.File`.
* Renamed `routers.Route.Swagger` to `routers.Route.Spec`.
### v0.51.0
* Type `openapi3filter.Route` moved to `routers` (and `Route.Handler` was dropped. See https://github.com/getkin/kin-openapi/issues/329)
* Type `openapi3filter.RouteError` moved to `routers` (so did `ErrPathNotFound` and `ErrMethodNotAllowed` which are now `RouteError`s)
* Routers' `FindRoute(...)` method now takes only one argument: `*http.Request`
* `getkin/kin-openapi/openapi3filter.Router` moved to `getkin/kin-openapi/routers/legacy`
* `openapi3filter.NewRouter()` and its related `WithSwaggerFromFile(string)`, `WithSwagger(*openapi3.Swagger)`, `AddSwaggerFromFile(string)` and `AddSwagger(*openapi3.Swagger)` are all replaced with a single `.NewRouter(*openapi3.Swagger)`
* NOTE: the `NewRouter(doc)` call now requires that the user ensures `doc` is valid (`doc.Validate() != nil`). This used to be asserted.
### v0.47.0
Field `(*openapi3.SwaggerLoader).LoadSwaggerFromURIFunc` of type `func(*openapi3.SwaggerLoader, *url.URL) (*openapi3.Swagger, error)` was removed after the addition of the field `(*openapi3.SwaggerLoader).ReadFromURIFunc` of type `func(*openapi3.SwaggerLoader, *url.URL) ([]byte, error)`.
kin-openapi-0.124.0/cmd/ 0000775 0000000 0000000 00000000000 14604223742 0014637 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/cmd/validate/ 0000775 0000000 0000000 00000000000 14604223742 0016430 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/cmd/validate/main.go 0000664 0000000 0000000 00000005776 14604223742 0017722 0 ustar 00root root 0000000 0000000 package main
import (
"flag"
"log"
"os"
"strings"
"github.com/invopop/yaml"
"github.com/getkin/kin-openapi/openapi2"
"github.com/getkin/kin-openapi/openapi3"
)
var (
defaultCircular = openapi3.CircularReferenceCounter
circular = flag.Int("circular", defaultCircular, "bump this (upper) limit when there's trouble with cyclic schema references")
)
var (
defaultDefaults = true
defaults = flag.Bool("defaults", defaultDefaults, "when false, disables schemas' default field validation")
)
var (
defaultExamples = true
examples = flag.Bool("examples", defaultExamples, "when false, disables all example schema validation")
)
var (
defaultExt = false
ext = flag.Bool("ext", defaultExt, "enables visiting other files")
)
var (
defaultPatterns = true
patterns = flag.Bool("patterns", defaultPatterns, "when false, allows schema patterns unsupported by the Go regexp engine")
)
func main() {
flag.Parse()
filename := flag.Arg(0)
if len(flag.Args()) != 1 || filename == "" {
log.Fatalf("Usage: go run github.com/getkin/kin-openapi/cmd/validate@latest [--circular] [--defaults] [--examples] [--ext] [--patterns] -- \nGot: %+v\n", os.Args)
}
data, err := os.ReadFile(filename)
if err != nil {
log.Fatal(err)
}
var vd struct {
OpenAPI string `json:"openapi" yaml:"openapi"`
Swagger string `json:"swagger" yaml:"swagger"`
}
if err := yaml.Unmarshal(data, &vd); err != nil {
log.Fatal(err)
}
switch {
case vd.OpenAPI == "3" || strings.HasPrefix(vd.OpenAPI, "3."):
openapi3.CircularReferenceCounter = *circular
loader := openapi3.NewLoader()
loader.IsExternalRefsAllowed = *ext
var doc *openapi3.T
if filename == "-" {
doc, err = loader.LoadFromStdin()
} else {
doc, err = loader.LoadFromFile(filename)
}
if err != nil {
log.Fatalln("Loading error:", err)
}
var opts []openapi3.ValidationOption
if !*defaults {
opts = append(opts, openapi3.DisableSchemaDefaultsValidation())
}
if !*examples {
opts = append(opts, openapi3.DisableExamplesValidation())
}
if !*patterns {
opts = append(opts, openapi3.DisableSchemaPatternValidation())
}
if err = doc.Validate(loader.Context, opts...); err != nil {
log.Fatalln("Validation error:", err)
}
case vd.OpenAPI == "2" || strings.HasPrefix(vd.OpenAPI, "2."),
vd.Swagger == "2" || strings.HasPrefix(vd.Swagger, "2."):
if *circular != defaultCircular {
log.Fatal("Flag --circular is only for OpenAPIv3")
}
if *defaults != defaultDefaults {
log.Fatal("Flag --defaults is only for OpenAPIv3")
}
if *examples != defaultExamples {
log.Fatal("Flag --examples is only for OpenAPIv3")
}
if *ext != defaultExt {
log.Fatal("Flag --ext is only for OpenAPIv3")
}
if *patterns != defaultPatterns {
log.Fatal("Flag --patterns is only for OpenAPIv3")
}
var doc openapi2.T
if err := yaml.Unmarshal(data, &doc); err != nil {
log.Fatalln("Loading error:", err)
}
default:
log.Fatal("Missing or incorrect 'openapi' or 'swagger' field")
}
}
kin-openapi-0.124.0/docs.sh 0000775 0000000 0000000 00000001302 14604223742 0015357 0 ustar 00root root 0000000 0000000 #!/bin/bash -eux
set -o pipefail
outdir=.github/docs
mkdir -p "$outdir"
for pkgpath in $(git ls-files | grep / | while read -r path; do dirname "$path"; done | sort -u | grep -vE '[.]git|testdata|internal|cmd/'); do
echo $pkgpath
go doc -all ./"$pkgpath" | tee "$outdir/${pkgpath////_}.txt"
done
git --no-pager diff -- .github/docs/
count_missing_mentions() {
local errors=0
for thing in $(git --no-pager diff -- .github/docs/ \
| grep -vE '[-]{3}' \
| grep -Eo '^-[^ ]+ ([^ (]+)[ (]' \
| sed 's%(% %' \
| cut -d' ' -f2); do
if ! grep -A999999 '## Sub-v0 breaking API changes' README.md | grep -F "$thing"; then
((errors++)) || true
fi
done
return $errors
}
count_missing_mentions
kin-openapi-0.124.0/go.mod 0000664 0000000 0000000 00000001174 14604223742 0015205 0 ustar 00root root 0000000 0000000 module github.com/getkin/kin-openapi
go 1.20
require (
github.com/go-openapi/jsonpointer v0.20.2
github.com/gorilla/mux v1.8.1
github.com/invopop/yaml v0.2.0
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
github.com/perimeterx/marshmallow v1.1.5
github.com/stretchr/testify v1.8.4
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-openapi/swag v0.22.8 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
)
kin-openapi-0.124.0/go.sum 0000664 0000000 0000000 00000005520 14604223742 0015231 0 ustar 00root root 0000000 0000000 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q=
github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs=
github.com/go-openapi/swag v0.22.8 h1:/9RjDSQ0vbFR+NyjGMkFTsA1IA0fmhKSThmfGZjicbw=
github.com/go-openapi/swag v0.22.8/go.mod h1:6QT22icPLEqAM/z/TChgb4WAveCHF92+2gF0CNjHpPI=
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY=
github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
kin-openapi-0.124.0/maps.sh 0000775 0000000 0000000 00000013301 14604223742 0015371 0 ustar 00root root 0000000 0000000 #!/bin/bash -eux
set -o pipefail
maplike=./openapi3/maplike.go
maplike_test=./openapi3/maplike_test.go
types=()
types+=('*Responses')
types+=('*Callback')
types+=('*Paths')
value_types=()
value_types+=('*ResponseRef')
value_types+=('*PathItem')
value_types+=('*PathItem')
deref_vs=()
deref_vs+=('*Response = v.Value')
deref_vs+=('*PathItem = v')
deref_vs+=('*PathItem = v')
names=()
names+=('responses')
names+=('callback')
names+=('paths')
[[ "${#types[@]}" = "${#value_types[@]}" ]]
[[ "${#types[@]}" = "${#deref_vs[@]}" ]]
[[ "${#types[@]}" = "${#names[@]}" ]]
[[ "${#types[@]}" = "$(git grep -InF ' m map[string]*' -- openapi3/loader.go | wc -l)" ]]
maplike_header() {
cat <"$maplike"
package openapi3
import (
"encoding/json"
"sort"
"strings"
"github.com/go-openapi/jsonpointer"
)
EOF
}
test_header() {
cat <"$maplike_test"
package openapi3
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestMaplikeMethods(t *testing.T) {
t.Parallel()
EOF
}
test_footer() {
echo "}" >>"$maplike_test"
}
maplike_NewWithCapa() {
cat <>"$maplike"
// New${type#'*'}WithCapacity builds a ${name} object of the given capacity.
func New${type#'*'}WithCapacity(cap int) ${type} {
if cap == 0 {
return &${type#'*'}{m: make(map[string]${value_type})}
}
return &${type#'*'}{m: make(map[string]${value_type}, cap)}
}
EOF
}
maplike_ValueSetLenDelete() {
cat <>"$maplike"
// Value returns the ${name} for key or nil
func (${name} ${type}) Value(key string) ${value_type} {
if ${name}.Len() == 0 {
return nil
}
return ${name}.m[key]
}
// Set adds or replaces key 'key' of '${name}' with 'value'.
// Note: '${name}' MUST be non-nil
func (${name} ${type}) Set(key string, value ${value_type}) {
if ${name}.m == nil {
${name}.m = make(map[string]${value_type})
}
${name}.m[key] = value
}
// Len returns the amount of keys in ${name} excluding ${name}.Extensions.
func (${name} ${type}) Len() int {
if ${name} == nil || ${name}.m == nil {
return 0
}
return len(${name}.m)
}
// Delete removes the entry associated with key 'key' from '${name}'.
func (${name} ${type}) Delete(key string) {
if ${name} != nil && ${name}.m != nil {
delete(${name}.m, key)
}
}
// Map returns ${name} as a 'map'.
// Note: iteration on Go maps is not ordered.
func (${name} ${type}) Map() (m map[string]${value_type}) {
if ${name} == nil || len(${name}.m) == 0 {
return make(map[string]${value_type})
}
m = make(map[string]${value_type}, len(${name}.m))
for k, v := range ${name}.m {
m[k] = v
}
return
}
EOF
}
maplike_Pointable() {
cat <>"$maplike"
var _ jsonpointer.JSONPointable = (${type})(nil)
// JSONLookup implements https://github.com/go-openapi/jsonpointer#JSONPointable
func (${name} ${type#'*'}) JSONLookup(token string) (interface{}, error) {
if v := ${name}.Value(token); v == nil {
vv, _, err := jsonpointer.GetForToken(${name}.Extensions, token)
return vv, err
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
var vv ${deref_v}
return vv, nil
}
}
EOF
}
maplike_UnMarsh() {
cat <>"$maplike"
// MarshalJSON returns the JSON encoding of ${type#'*'}.
func (${name} ${type}) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{}, ${name}.Len()+len(${name}.Extensions))
for k, v := range ${name}.Extensions {
m[k] = v
}
for k, v := range ${name}.Map() {
m[k] = v
}
return json.Marshal(m)
}
// UnmarshalJSON sets ${type#'*'} to a copy of data.
func (${name} ${type}) UnmarshalJSON(data []byte) (err error) {
var m map[string]interface{}
if err = json.Unmarshal(data, &m); err != nil {
return
}
ks := make([]string, 0, len(m))
for k := range m {
ks = append(ks, k)
}
sort.Strings(ks)
x := ${type#'*'}{
Extensions: make(map[string]interface{}),
m: make(map[string]${value_type}, len(m)),
}
for _, k := range ks {
v := m[k]
if strings.HasPrefix(k, "x-") {
x.Extensions[k] = v
continue
}
var data []byte
if data, err = json.Marshal(v); err != nil {
return
}
var vv ${value_type#'*'}
if err = vv.UnmarshalJSON(data); err != nil {
return
}
x.m[k] = &vv
}
*${name} = x
return
}
EOF
}
test_body() {
cat <>"$maplike_test"
t.Run("${type}", func(t *testing.T) {
t.Parallel()
t.Run("nil", func(t *testing.T) {
x := (${type})(nil)
require.Equal(t, 0, x.Len())
require.Equal(t, map[string]${value_type}{}, x.Map())
require.Equal(t, (${value_type})(nil), x.Value("key"))
require.Panics(t, func() { x.Set("key", &${value_type#'*'}{}) })
require.NotPanics(t, func() { x.Delete("key") })
})
t.Run("nonnil", func(t *testing.T) {
x := &${type#'*'}{}
require.Equal(t, 0, x.Len())
require.Equal(t, map[string]${value_type}{}, x.Map())
require.Equal(t, (${value_type})(nil), x.Value("key"))
x.Set("key", &${value_type#'*'}{})
require.Equal(t, 1, x.Len())
require.Equal(t, map[string]${value_type}{"key": {}}, x.Map())
require.Equal(t, &${value_type#'*'}{}, x.Value("key"))
x.Delete("key")
require.Equal(t, 0, x.Len())
require.Equal(t, map[string]${value_type}{}, x.Map())
require.Equal(t, (${value_type})(nil), x.Value("key"))
require.NotPanics(t, func() { x.Delete("key") })
})
})
EOF
}
maplike_header
test_header
for i in "${!types[@]}"; do
type=${types[$i]}
value_type=${value_types[$i]}
deref_v=${deref_vs[$i]}
name=${names[$i]}
type="$type" name="$name" value_type="$value_type" maplike_NewWithCapa
type="$type" name="$name" value_type="$value_type" maplike_ValueSetLenDelete
type="$type" name="$name" deref_v="$deref_v" maplike_Pointable
type="$type" name="$name" value_type="$value_type" maplike_UnMarsh
[[ $((i+1)) != "${#types[@]}" ]] && echo >>"$maplike"
type="$type" value_type="$value_type" test_body
done
test_footer
kin-openapi-0.124.0/openapi2/ 0000775 0000000 0000000 00000000000 14604223742 0015611 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi2/doc.go 0000664 0000000 0000000 00000000502 14604223742 0016702 0 ustar 00root root 0000000 0000000 // Package openapi2 parses and writes OpenAPIv2 specification documents.
//
// Does not cover all elements of OpenAPIv2.
// When OpenAPI version 3 is backwards-compatible with version 2, version 3 elements have been used.
//
// See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md
package openapi2
kin-openapi-0.124.0/openapi2/header.go 0000664 0000000 0000000 00000000540 14604223742 0017367 0 ustar 00root root 0000000 0000000 package openapi2
type Header struct {
Parameter
}
// MarshalJSON returns the JSON encoding of Header.
func (header Header) MarshalJSON() ([]byte, error) {
return header.Parameter.MarshalJSON()
}
// UnmarshalJSON sets Header to a copy of data.
func (header *Header) UnmarshalJSON(data []byte) error {
return header.Parameter.UnmarshalJSON(data)
}
kin-openapi-0.124.0/openapi2/marsh.go 0000664 0000000 0000000 00000001311 14604223742 0017246 0 ustar 00root root 0000000 0000000 package openapi2
import (
"encoding/json"
"fmt"
"strings"
"github.com/invopop/yaml"
)
func unmarshalError(jsonUnmarshalErr error) error {
if before, after, found := strings.Cut(jsonUnmarshalErr.Error(), "Bis"); found && before != "" && after != "" {
before = strings.ReplaceAll(before, " Go struct ", " ")
return fmt.Errorf("%s%s", before, strings.ReplaceAll(after, "Bis", ""))
}
return jsonUnmarshalErr
}
func unmarshal(data []byte, v interface{}) error {
// See https://github.com/getkin/kin-openapi/issues/680
if err := json.Unmarshal(data, v); err != nil {
// UnmarshalStrict(data, v) TODO: investigate how ymlv3 handles duplicate map keys
return yaml.Unmarshal(data, v)
}
return nil
}
kin-openapi-0.124.0/openapi2/marsh_test.go 0000664 0000000 0000000 00000002040 14604223742 0020305 0 ustar 00root root 0000000 0000000 package openapi2
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestUnmarshalError(t *testing.T) {
{
v2 := []byte(`
openapi: '2.0'
info:
version: '1.10'
title: title
paths:
"/ping":
post:
consumes:
- multipart/form-data
parameters:
name: file # <-- Missing dash
in: formData
description: file
required: true
type: file
responses:
'200':
description: OK
`[1:])
var doc T
err := unmarshal(v2, &doc)
require.ErrorContains(t, err, `json: cannot unmarshal object into field Operation.parameters of type openapi2.Parameters`)
}
v2 := []byte(`
openapi: '2.0'
info:
version: '1.10'
title: title
paths:
"/ping":
post:
consumes:
- multipart/form-data
parameters:
- name: file # <--
in: formData
description: file
required: true
type: file
responses:
'200':
description: OK
`[1:])
var doc T
err := unmarshal(v2, &doc)
require.NoError(t, err)
}
kin-openapi-0.124.0/openapi2/openapi2.go 0000664 0000000 0000000 00000007632 14604223742 0017665 0 ustar 00root root 0000000 0000000 package openapi2
import (
"encoding/json"
"github.com/getkin/kin-openapi/openapi3"
)
// T is the root of an OpenAPI v2 document
type T struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Swagger string `json:"swagger" yaml:"swagger"` // required
Info openapi3.Info `json:"info" yaml:"info"` // required
ExternalDocs *openapi3.ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
Schemes []string `json:"schemes,omitempty" yaml:"schemes,omitempty"`
Consumes []string `json:"consumes,omitempty" yaml:"consumes,omitempty"`
Produces []string `json:"produces,omitempty" yaml:"produces,omitempty"`
Host string `json:"host,omitempty" yaml:"host,omitempty"`
BasePath string `json:"basePath,omitempty" yaml:"basePath,omitempty"`
Paths map[string]*PathItem `json:"paths,omitempty" yaml:"paths,omitempty"`
Definitions map[string]*openapi3.SchemaRef `json:"definitions,omitempty" yaml:"definitions,omitempty"`
Parameters map[string]*Parameter `json:"parameters,omitempty" yaml:"parameters,omitempty"`
Responses map[string]*Response `json:"responses,omitempty" yaml:"responses,omitempty"`
SecurityDefinitions map[string]*SecurityScheme `json:"securityDefinitions,omitempty" yaml:"securityDefinitions,omitempty"`
Security SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"`
Tags openapi3.Tags `json:"tags,omitempty" yaml:"tags,omitempty"`
}
// MarshalJSON returns the JSON encoding of T.
func (doc T) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{}, 15+len(doc.Extensions))
for k, v := range doc.Extensions {
m[k] = v
}
m["swagger"] = doc.Swagger
m["info"] = doc.Info
if x := doc.ExternalDocs; x != nil {
m["externalDocs"] = x
}
if x := doc.Schemes; len(x) != 0 {
m["schemes"] = x
}
if x := doc.Consumes; len(x) != 0 {
m["consumes"] = x
}
if x := doc.Produces; len(x) != 0 {
m["produces"] = x
}
if x := doc.Host; x != "" {
m["host"] = x
}
if x := doc.BasePath; x != "" {
m["basePath"] = x
}
if x := doc.Paths; len(x) != 0 {
m["paths"] = x
}
if x := doc.Definitions; len(x) != 0 {
m["definitions"] = x
}
if x := doc.Parameters; len(x) != 0 {
m["parameters"] = x
}
if x := doc.Responses; len(x) != 0 {
m["responses"] = x
}
if x := doc.SecurityDefinitions; len(x) != 0 {
m["securityDefinitions"] = x
}
if x := doc.Security; len(x) != 0 {
m["security"] = x
}
if x := doc.Tags; len(x) != 0 {
m["tags"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets T to a copy of data.
func (doc *T) UnmarshalJSON(data []byte) error {
type TBis T
var x TBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "swagger")
delete(x.Extensions, "info")
delete(x.Extensions, "externalDocs")
delete(x.Extensions, "schemes")
delete(x.Extensions, "consumes")
delete(x.Extensions, "produces")
delete(x.Extensions, "host")
delete(x.Extensions, "basePath")
delete(x.Extensions, "paths")
delete(x.Extensions, "definitions")
delete(x.Extensions, "parameters")
delete(x.Extensions, "responses")
delete(x.Extensions, "securityDefinitions")
delete(x.Extensions, "security")
delete(x.Extensions, "tags")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*doc = T(x)
return nil
}
func (doc *T) AddOperation(path string, method string, operation *Operation) {
if doc.Paths == nil {
doc.Paths = make(map[string]*PathItem)
}
pathItem := doc.Paths[path]
if pathItem == nil {
pathItem = &PathItem{}
doc.Paths[path] = pathItem
}
pathItem.SetOperation(method, operation)
}
kin-openapi-0.124.0/openapi2/openapi2_test.go 0000664 0000000 0000000 00000002106 14604223742 0020713 0 ustar 00root root 0000000 0000000 package openapi2_test
import (
"encoding/json"
"fmt"
"os"
"reflect"
"github.com/invopop/yaml"
"github.com/getkin/kin-openapi/openapi2"
)
func Example() {
input, err := os.ReadFile("testdata/swagger.json")
if err != nil {
panic(err)
}
var doc openapi2.T
if err = json.Unmarshal(input, &doc); err != nil {
panic(err)
}
if doc.ExternalDocs.Description != "Find out more about Swagger" {
panic(`doc.ExternalDocs was parsed incorrectly!`)
}
outputJSON, err := json.Marshal(doc)
if err != nil {
panic(err)
}
var docAgainFromJSON openapi2.T
if err = json.Unmarshal(outputJSON, &docAgainFromJSON); err != nil {
panic(err)
}
if !reflect.DeepEqual(doc, docAgainFromJSON) {
fmt.Println("objects doc & docAgainFromJSON should be the same")
}
outputYAML, err := yaml.Marshal(doc)
if err != nil {
panic(err)
}
var docAgainFromYAML openapi2.T
if err = yaml.Unmarshal(outputYAML, &docAgainFromYAML); err != nil {
panic(err)
}
if !reflect.DeepEqual(doc, docAgainFromYAML) {
fmt.Println("objects doc & docAgainFromYAML should be the same")
}
// Output:
}
kin-openapi-0.124.0/openapi2/operation.go 0000664 0000000 0000000 00000006002 14604223742 0020136 0 ustar 00root root 0000000 0000000 package openapi2
import (
"encoding/json"
"github.com/getkin/kin-openapi/openapi3"
)
type Operation struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Summary string `json:"summary,omitempty" yaml:"summary,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
ExternalDocs *openapi3.ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"`
OperationID string `json:"operationId,omitempty" yaml:"operationId,omitempty"`
Parameters Parameters `json:"parameters,omitempty" yaml:"parameters,omitempty"`
Responses map[string]*Response `json:"responses" yaml:"responses"`
Consumes []string `json:"consumes,omitempty" yaml:"consumes,omitempty"`
Produces []string `json:"produces,omitempty" yaml:"produces,omitempty"`
Schemes []string `json:"schemes,omitempty" yaml:"schemes,omitempty"`
Security *SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"`
}
// MarshalJSON returns the JSON encoding of Operation.
func (operation Operation) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{}, 12+len(operation.Extensions))
for k, v := range operation.Extensions {
m[k] = v
}
if x := operation.Summary; x != "" {
m["summary"] = x
}
if x := operation.Description; x != "" {
m["description"] = x
}
if x := operation.Deprecated; x {
m["deprecated"] = x
}
if x := operation.ExternalDocs; x != nil {
m["externalDocs"] = x
}
if x := operation.Tags; len(x) != 0 {
m["tags"] = x
}
if x := operation.OperationID; x != "" {
m["operationId"] = x
}
if x := operation.Parameters; len(x) != 0 {
m["parameters"] = x
}
m["responses"] = operation.Responses
if x := operation.Consumes; len(x) != 0 {
m["consumes"] = x
}
if x := operation.Produces; len(x) != 0 {
m["produces"] = x
}
if x := operation.Schemes; len(x) != 0 {
m["schemes"] = x
}
if x := operation.Security; x != nil {
m["security"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets Operation to a copy of data.
func (operation *Operation) UnmarshalJSON(data []byte) error {
type OperationBis Operation
var x OperationBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "summary")
delete(x.Extensions, "description")
delete(x.Extensions, "deprecated")
delete(x.Extensions, "externalDocs")
delete(x.Extensions, "tags")
delete(x.Extensions, "operationId")
delete(x.Extensions, "parameters")
delete(x.Extensions, "responses")
delete(x.Extensions, "consumes")
delete(x.Extensions, "produces")
delete(x.Extensions, "schemes")
delete(x.Extensions, "security")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*operation = Operation(x)
return nil
}
kin-openapi-0.124.0/openapi2/parameter.go 0000664 0000000 0000000 00000013167 14604223742 0020130 0 ustar 00root root 0000000 0000000 package openapi2
import (
"encoding/json"
"sort"
"github.com/getkin/kin-openapi/openapi3"
)
type Parameters []*Parameter
var _ sort.Interface = Parameters{}
func (ps Parameters) Len() int { return len(ps) }
func (ps Parameters) Swap(i, j int) { ps[i], ps[j] = ps[j], ps[i] }
func (ps Parameters) Less(i, j int) bool {
if ps[i].Name != ps[j].Name {
return ps[i].Name < ps[j].Name
}
if ps[i].In != ps[j].In {
return ps[i].In < ps[j].In
}
return ps[i].Ref < ps[j].Ref
}
type Parameter struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
In string `json:"in,omitempty" yaml:"in,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
CollectionFormat string `json:"collectionFormat,omitempty" yaml:"collectionFormat,omitempty"`
Type *openapi3.Types `json:"type,omitempty" yaml:"type,omitempty"`
Format string `json:"format,omitempty" yaml:"format,omitempty"`
Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"`
AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"`
Required bool `json:"required,omitempty" yaml:"required,omitempty"`
UniqueItems bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"`
ExclusiveMin bool `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"`
ExclusiveMax bool `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"`
Schema *openapi3.SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"`
Items *openapi3.SchemaRef `json:"items,omitempty" yaml:"items,omitempty"`
Enum []interface{} `json:"enum,omitempty" yaml:"enum,omitempty"`
MultipleOf *float64 `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"`
Minimum *float64 `json:"minimum,omitempty" yaml:"minimum,omitempty"`
Maximum *float64 `json:"maximum,omitempty" yaml:"maximum,omitempty"`
MaxLength *uint64 `json:"maxLength,omitempty" yaml:"maxLength,omitempty"`
MaxItems *uint64 `json:"maxItems,omitempty" yaml:"maxItems,omitempty"`
MinLength uint64 `json:"minLength,omitempty" yaml:"minLength,omitempty"`
MinItems uint64 `json:"minItems,omitempty" yaml:"minItems,omitempty"`
Default interface{} `json:"default,omitempty" yaml:"default,omitempty"`
}
// MarshalJSON returns the JSON encoding of Parameter.
func (parameter Parameter) MarshalJSON() ([]byte, error) {
if ref := parameter.Ref; ref != "" {
return json.Marshal(openapi3.Ref{Ref: ref})
}
m := make(map[string]interface{}, 24+len(parameter.Extensions))
for k, v := range parameter.Extensions {
m[k] = v
}
if x := parameter.In; x != "" {
m["in"] = x
}
if x := parameter.Name; x != "" {
m["name"] = x
}
if x := parameter.Description; x != "" {
m["description"] = x
}
if x := parameter.CollectionFormat; x != "" {
m["collectionFormat"] = x
}
if x := parameter.Type; x != nil {
m["type"] = x
}
if x := parameter.Format; x != "" {
m["format"] = x
}
if x := parameter.Pattern; x != "" {
m["pattern"] = x
}
if x := parameter.AllowEmptyValue; x {
m["allowEmptyValue"] = x
}
if x := parameter.Required; x {
m["required"] = x
}
if x := parameter.UniqueItems; x {
m["uniqueItems"] = x
}
if x := parameter.ExclusiveMin; x {
m["exclusiveMinimum"] = x
}
if x := parameter.ExclusiveMax; x {
m["exclusiveMaximum"] = x
}
if x := parameter.Schema; x != nil {
m["schema"] = x
}
if x := parameter.Items; x != nil {
m["items"] = x
}
if x := parameter.Enum; x != nil {
m["enum"] = x
}
if x := parameter.MultipleOf; x != nil {
m["multipleOf"] = x
}
if x := parameter.Minimum; x != nil {
m["minimum"] = x
}
if x := parameter.Maximum; x != nil {
m["maximum"] = x
}
if x := parameter.MaxLength; x != nil {
m["maxLength"] = x
}
if x := parameter.MaxItems; x != nil {
m["maxItems"] = x
}
if x := parameter.MinLength; x != 0 {
m["minLength"] = x
}
if x := parameter.MinItems; x != 0 {
m["minItems"] = x
}
if x := parameter.Default; x != nil {
m["default"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets Parameter to a copy of data.
func (parameter *Parameter) UnmarshalJSON(data []byte) error {
type ParameterBis Parameter
var x ParameterBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "$ref")
delete(x.Extensions, "in")
delete(x.Extensions, "name")
delete(x.Extensions, "description")
delete(x.Extensions, "collectionFormat")
delete(x.Extensions, "type")
delete(x.Extensions, "format")
delete(x.Extensions, "pattern")
delete(x.Extensions, "allowEmptyValue")
delete(x.Extensions, "required")
delete(x.Extensions, "uniqueItems")
delete(x.Extensions, "exclusiveMinimum")
delete(x.Extensions, "exclusiveMaximum")
delete(x.Extensions, "schema")
delete(x.Extensions, "items")
delete(x.Extensions, "enum")
delete(x.Extensions, "multipleOf")
delete(x.Extensions, "minimum")
delete(x.Extensions, "maximum")
delete(x.Extensions, "maxLength")
delete(x.Extensions, "maxItems")
delete(x.Extensions, "minLength")
delete(x.Extensions, "minItems")
delete(x.Extensions, "default")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*parameter = Parameter(x)
return nil
}
kin-openapi-0.124.0/openapi2/path_item.go 0000664 0000000 0000000 00000007536 14604223742 0020125 0 ustar 00root root 0000000 0000000 package openapi2
import (
"encoding/json"
"fmt"
"net/http"
"github.com/getkin/kin-openapi/openapi3"
)
type PathItem struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
Delete *Operation `json:"delete,omitempty" yaml:"delete,omitempty"`
Get *Operation `json:"get,omitempty" yaml:"get,omitempty"`
Head *Operation `json:"head,omitempty" yaml:"head,omitempty"`
Options *Operation `json:"options,omitempty" yaml:"options,omitempty"`
Patch *Operation `json:"patch,omitempty" yaml:"patch,omitempty"`
Post *Operation `json:"post,omitempty" yaml:"post,omitempty"`
Put *Operation `json:"put,omitempty" yaml:"put,omitempty"`
Parameters Parameters `json:"parameters,omitempty" yaml:"parameters,omitempty"`
}
// MarshalJSON returns the JSON encoding of PathItem.
func (pathItem PathItem) MarshalJSON() ([]byte, error) {
if ref := pathItem.Ref; ref != "" {
return json.Marshal(openapi3.Ref{Ref: ref})
}
m := make(map[string]interface{}, 8+len(pathItem.Extensions))
for k, v := range pathItem.Extensions {
m[k] = v
}
if x := pathItem.Delete; x != nil {
m["delete"] = x
}
if x := pathItem.Get; x != nil {
m["get"] = x
}
if x := pathItem.Head; x != nil {
m["head"] = x
}
if x := pathItem.Options; x != nil {
m["options"] = x
}
if x := pathItem.Patch; x != nil {
m["patch"] = x
}
if x := pathItem.Post; x != nil {
m["post"] = x
}
if x := pathItem.Put; x != nil {
m["put"] = x
}
if x := pathItem.Parameters; len(x) != 0 {
m["parameters"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets PathItem to a copy of data.
func (pathItem *PathItem) UnmarshalJSON(data []byte) error {
type PathItemBis PathItem
var x PathItemBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "$ref")
delete(x.Extensions, "delete")
delete(x.Extensions, "get")
delete(x.Extensions, "head")
delete(x.Extensions, "options")
delete(x.Extensions, "patch")
delete(x.Extensions, "post")
delete(x.Extensions, "put")
delete(x.Extensions, "parameters")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*pathItem = PathItem(x)
return nil
}
func (pathItem *PathItem) Operations() map[string]*Operation {
operations := make(map[string]*Operation)
if v := pathItem.Delete; v != nil {
operations[http.MethodDelete] = v
}
if v := pathItem.Get; v != nil {
operations[http.MethodGet] = v
}
if v := pathItem.Head; v != nil {
operations[http.MethodHead] = v
}
if v := pathItem.Options; v != nil {
operations[http.MethodOptions] = v
}
if v := pathItem.Patch; v != nil {
operations[http.MethodPatch] = v
}
if v := pathItem.Post; v != nil {
operations[http.MethodPost] = v
}
if v := pathItem.Put; v != nil {
operations[http.MethodPut] = v
}
return operations
}
func (pathItem *PathItem) GetOperation(method string) *Operation {
switch method {
case http.MethodDelete:
return pathItem.Delete
case http.MethodGet:
return pathItem.Get
case http.MethodHead:
return pathItem.Head
case http.MethodOptions:
return pathItem.Options
case http.MethodPatch:
return pathItem.Patch
case http.MethodPost:
return pathItem.Post
case http.MethodPut:
return pathItem.Put
default:
panic(fmt.Errorf("unsupported HTTP method %q", method))
}
}
func (pathItem *PathItem) SetOperation(method string, operation *Operation) {
switch method {
case http.MethodDelete:
pathItem.Delete = operation
case http.MethodGet:
pathItem.Get = operation
case http.MethodHead:
pathItem.Head = operation
case http.MethodOptions:
pathItem.Options = operation
case http.MethodPatch:
pathItem.Patch = operation
case http.MethodPost:
pathItem.Post = operation
case http.MethodPut:
pathItem.Put = operation
default:
panic(fmt.Errorf("unsupported HTTP method %q", method))
}
}
kin-openapi-0.124.0/openapi2/response.go 0000664 0000000 0000000 00000003275 14604223742 0020005 0 ustar 00root root 0000000 0000000 package openapi2
import (
"encoding/json"
"github.com/getkin/kin-openapi/openapi3"
)
type Response struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Schema *openapi3.SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"`
Headers map[string]*Header `json:"headers,omitempty" yaml:"headers,omitempty"`
Examples map[string]interface{} `json:"examples,omitempty" yaml:"examples,omitempty"`
}
// MarshalJSON returns the JSON encoding of Response.
func (response Response) MarshalJSON() ([]byte, error) {
if ref := response.Ref; ref != "" {
return json.Marshal(openapi3.Ref{Ref: ref})
}
m := make(map[string]interface{}, 4+len(response.Extensions))
for k, v := range response.Extensions {
m[k] = v
}
if x := response.Description; x != "" {
m["description"] = x
}
if x := response.Schema; x != nil {
m["schema"] = x
}
if x := response.Headers; len(x) != 0 {
m["headers"] = x
}
if x := response.Examples; len(x) != 0 {
m["examples"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets Response to a copy of data.
func (response *Response) UnmarshalJSON(data []byte) error {
type ResponseBis Response
var x ResponseBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "$ref")
delete(x.Extensions, "description")
delete(x.Extensions, "schema")
delete(x.Extensions, "headers")
delete(x.Extensions, "examples")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*response = Response(x)
return nil
}
kin-openapi-0.124.0/openapi2/security_scheme.go 0000664 0000000 0000000 00000005332 14604223742 0021336 0 ustar 00root root 0000000 0000000 package openapi2
import (
"encoding/json"
"github.com/getkin/kin-openapi/openapi3"
)
type SecurityRequirements []map[string][]string
type SecurityScheme struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Type string `json:"type,omitempty" yaml:"type,omitempty"`
In string `json:"in,omitempty" yaml:"in,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Flow string `json:"flow,omitempty" yaml:"flow,omitempty"`
AuthorizationURL string `json:"authorizationUrl,omitempty" yaml:"authorizationUrl,omitempty"`
TokenURL string `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"`
Scopes map[string]string `json:"scopes,omitempty" yaml:"scopes,omitempty"`
Tags openapi3.Tags `json:"tags,omitempty" yaml:"tags,omitempty"`
}
// MarshalJSON returns the JSON encoding of SecurityScheme.
func (securityScheme SecurityScheme) MarshalJSON() ([]byte, error) {
if ref := securityScheme.Ref; ref != "" {
return json.Marshal(openapi3.Ref{Ref: ref})
}
m := make(map[string]interface{}, 10+len(securityScheme.Extensions))
for k, v := range securityScheme.Extensions {
m[k] = v
}
if x := securityScheme.Description; x != "" {
m["description"] = x
}
if x := securityScheme.Type; x != "" {
m["type"] = x
}
if x := securityScheme.In; x != "" {
m["in"] = x
}
if x := securityScheme.Name; x != "" {
m["name"] = x
}
if x := securityScheme.Flow; x != "" {
m["flow"] = x
}
if x := securityScheme.AuthorizationURL; x != "" {
m["authorizationUrl"] = x
}
if x := securityScheme.TokenURL; x != "" {
m["tokenUrl"] = x
}
if x := securityScheme.Scopes; len(x) != 0 {
m["scopes"] = x
}
if x := securityScheme.Tags; len(x) != 0 {
m["tags"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets SecurityScheme to a copy of data.
func (securityScheme *SecurityScheme) UnmarshalJSON(data []byte) error {
type SecuritySchemeBis SecurityScheme
var x SecuritySchemeBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "$ref")
delete(x.Extensions, "description")
delete(x.Extensions, "type")
delete(x.Extensions, "in")
delete(x.Extensions, "name")
delete(x.Extensions, "flow")
delete(x.Extensions, "authorizationUrl")
delete(x.Extensions, "tokenUrl")
delete(x.Extensions, "scopes")
delete(x.Extensions, "tags")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*securityScheme = SecurityScheme(x)
return nil
}
kin-openapi-0.124.0/openapi2/testdata/ 0000775 0000000 0000000 00000000000 14604223742 0017422 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi2/testdata/swagger.json 0000664 0000000 0000000 00000032152 14604223742 0021757 0 ustar 00root root 0000000 0000000 {"swagger":"2.0","info":{"title":"Swagger Petstore","description":"This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.","termsOfService":"http://swagger.io/terms/","contact":{"email":"apiteam@swagger.io"},"license":{"name":"Apache 2.0","url":"http://www.apache.org/licenses/LICENSE-2.0.html"},"version":"1.0.3"},"externalDocs":{"description":"Find out more about Swagger","url":"http://swagger.io"},"schemes":["https","http"],"host":"petstore.swagger.io","basePath":"/v2","paths":{"/pet":{"post":{"summary":"Add a new pet to the store","tags":["pet"],"operationId":"addPet","parameters":[{"in":"body","name":"body","description":"Pet object that needs to be added to the store","required":true,"schema":{"$ref":"#/definitions/Pet"}}],"responses":{"405":{"description":"Invalid input"}},"consumes":["application/json","application/xml"],"produces":["application/json","application/xml"],"security":[{"petstore_auth":["write:pets","read:pets"]}]},"put":{"summary":"Update an existing pet","tags":["pet"],"operationId":"updatePet","parameters":[{"in":"body","name":"body","description":"Pet object that needs to be added to the store","required":true,"schema":{"$ref":"#/definitions/Pet"}}],"responses":{"400":{"description":"Invalid ID supplied"},"404":{"description":"Pet not found"},"405":{"description":"Validation exception"}},"consumes":["application/json","application/xml"],"produces":["application/json","application/xml"],"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/findByStatus":{"get":{"summary":"Finds Pets by status","description":"Multiple status values can be provided with comma separated strings","tags":["pet"],"operationId":"findPetsByStatus","parameters":[{"in":"query","name":"status","description":"Status values that need to be considered for filter","required":true,"type":"array","items":{"default":"available","enum":["available","pending","sold"],"type":"string"}}],"responses":{"200":{"description":"successful operation","schema":{"items":{"$ref":"#/definitions/Pet"},"type":"array"}},"400":{"description":"Invalid status value"}},"produces":["application/json","application/xml"],"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/findByTags":{"get":{"summary":"Finds Pets by tags","description":"Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.","tags":["pet"],"operationId":"findPetsByTags","parameters":[{"in":"query","name":"tags","description":"Tags to filter by","required":true,"type":"array","items":{"type":"string"}}],"responses":{"200":{"description":"successful operation","schema":{"items":{"$ref":"#/definitions/Pet"},"type":"array"}},"400":{"description":"Invalid tag value"}},"produces":["application/json","application/xml"],"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/{petId}":{"delete":{"summary":"Deletes a pet","tags":["pet"],"operationId":"deletePet","parameters":[{"in":"header","name":"api_key","type":"string"},{"in":"path","name":"petId","description":"Pet id to delete","required":true,"type":"integer","format":"int64"}],"responses":{"400":{"description":"Invalid ID supplied"},"404":{"description":"Pet not found"}},"produces":["application/json","application/xml"],"security":[{"petstore_auth":["write:pets","read:pets"]}]},"get":{"summary":"Find pet by ID","description":"Returns a single pet","tags":["pet"],"operationId":"getPetById","parameters":[{"in":"path","name":"petId","description":"ID of pet to return","required":true,"type":"integer","format":"int64"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/Pet"}},"400":{"description":"Invalid ID supplied"},"404":{"description":"Pet not found"}},"produces":["application/json","application/xml"],"security":[{"api_key":[]}]},"post":{"summary":"Updates a pet in the store with form data","tags":["pet"],"operationId":"updatePetWithForm","parameters":[{"in":"path","name":"petId","description":"ID of pet that needs to be updated","required":true,"type":"integer","format":"int64"},{"in":"formData","name":"name","description":"Updated name of the pet","type":"string"},{"in":"formData","name":"status","description":"Updated status of the pet","type":"string"}],"responses":{"405":{"description":"Invalid input"}},"consumes":["application/x-www-form-urlencoded"],"produces":["application/json","application/xml"],"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/{petId}/uploadImage":{"post":{"summary":"uploads an image","tags":["pet"],"operationId":"uploadFile","parameters":[{"in":"path","name":"petId","description":"ID of pet to update","required":true,"type":"integer","format":"int64"},{"in":"formData","name":"additionalMetadata","description":"Additional data to pass to server","type":"string"},{"in":"formData","name":"file","description":"file to upload","type":"file"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/ApiResponse"}}},"consumes":["multipart/form-data"],"produces":["application/json"],"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/store/inventory":{"get":{"summary":"Returns pet inventories by status","description":"Returns a map of status codes to quantities","tags":["store"],"operationId":"getInventory","responses":{"200":{"description":"successful operation","schema":{"additionalProperties":{"format":"int32","type":"integer"},"type":"object"}}},"produces":["application/json"],"security":[{"api_key":[]}]}},"/store/order":{"post":{"summary":"Place an order for a pet","tags":["store"],"operationId":"placeOrder","parameters":[{"in":"body","name":"body","description":"order placed for purchasing the pet","required":true,"schema":{"$ref":"#/definitions/Order"}}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/Order"}},"400":{"description":"Invalid Order"}},"consumes":["application/json"],"produces":["application/json","application/xml"]}},"/store/order/{orderId}":{"delete":{"summary":"Delete purchase order by ID","description":"For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors","tags":["store"],"operationId":"deleteOrder","parameters":[{"in":"path","name":"orderId","description":"ID of the order that needs to be deleted","required":true,"type":"integer","format":"int64","minimum":1}],"responses":{"400":{"description":"Invalid ID supplied"},"404":{"description":"Order not found"}},"produces":["application/json","application/xml"]},"get":{"summary":"Find purchase order by ID","description":"For valid response try integer IDs with value \u003e= 1 and \u003c= 10. Other values will generated exceptions","tags":["store"],"operationId":"getOrderById","parameters":[{"in":"path","name":"orderId","description":"ID of pet that needs to be fetched","required":true,"type":"integer","format":"int64","minimum":1,"maximum":10}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/Order"}},"400":{"description":"Invalid ID supplied"},"404":{"description":"Order not found"}},"produces":["application/json","application/xml"]}},"/user":{"post":{"summary":"Create user","description":"This can only be done by the logged in user.","tags":["user"],"operationId":"createUser","parameters":[{"in":"body","name":"body","description":"Created user object","required":true,"schema":{"$ref":"#/definitions/User"}}],"responses":{"default":{"description":"successful operation"}},"consumes":["application/json"],"produces":["application/json","application/xml"]}},"/user/createWithArray":{"post":{"summary":"Creates list of users with given input array","tags":["user"],"operationId":"createUsersWithArrayInput","parameters":[{"in":"body","name":"body","description":"List of user object","required":true,"schema":{"items":{"$ref":"#/definitions/User"},"type":"array"}}],"responses":{"default":{"description":"successful operation"}},"consumes":["application/json"],"produces":["application/json","application/xml"]}},"/user/createWithList":{"post":{"summary":"Creates list of users with given input array","tags":["user"],"operationId":"createUsersWithListInput","parameters":[{"in":"body","name":"body","description":"List of user object","required":true,"schema":{"items":{"$ref":"#/definitions/User"},"type":"array"}}],"responses":{"default":{"description":"successful operation"}},"consumes":["application/json"],"produces":["application/json","application/xml"]}},"/user/login":{"get":{"summary":"Logs user into the system","tags":["user"],"operationId":"loginUser","parameters":[{"in":"query","name":"username","description":"The user name for login","required":true,"type":"string"},{"in":"query","name":"password","description":"The password for login in clear text","required":true,"type":"string"}],"responses":{"200":{"description":"successful operation","schema":{"type":"string"},"headers":{"X-Expires-After":{"description":"date in UTC when token expires","type":"string"},"X-Rate-Limit":{"description":"calls per hour allowed by the user","type":"integer"}}},"400":{"description":"Invalid username/password supplied"}},"produces":["application/json","application/xml"]}},"/user/logout":{"get":{"summary":"Logs out current logged in user session","tags":["user"],"operationId":"logoutUser","responses":{"default":{"description":"successful operation"}},"produces":["application/json","application/xml"]}},"/user/{username}":{"delete":{"summary":"Delete user","description":"This can only be done by the logged in user.","tags":["user"],"operationId":"deleteUser","parameters":[{"in":"path","name":"username","description":"The name that needs to be deleted","required":true,"type":"string"}],"responses":{"400":{"description":"Invalid username supplied"},"404":{"description":"User not found"}},"produces":["application/json","application/xml"]},"get":{"summary":"Get user by user name","tags":["user"],"operationId":"getUserByName","parameters":[{"in":"path","name":"username","description":"The name that needs to be fetched. Use user1 for testing. ","required":true,"type":"string"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/User"}},"400":{"description":"Invalid username supplied"},"404":{"description":"User not found"}},"produces":["application/json","application/xml"]},"put":{"summary":"Updated user","description":"This can only be done by the logged in user.","tags":["user"],"operationId":"updateUser","parameters":[{"in":"path","name":"username","description":"name that need to be updated","required":true,"type":"string"},{"in":"body","name":"body","description":"Updated user object","required":true,"schema":{"$ref":"#/definitions/User"}}],"responses":{"400":{"description":"Invalid user supplied"},"404":{"description":"User not found"}},"consumes":["application/json"],"produces":["application/json","application/xml"]}}},"definitions":{"ApiResponse":{"properties":{"code":{"format":"int32","type":"integer"},"message":{"type":"string"},"type":{"type":"string"}},"type":"object"},"Category":{"properties":{"id":{"format":"int64","type":"integer"},"name":{"type":"string"}},"type":"object","xml":{"name":"Category"}},"Order":{"properties":{"complete":{"type":"boolean"},"id":{"format":"int64","type":"integer"},"petId":{"format":"int64","type":"integer"},"quantity":{"format":"int32","type":"integer"},"shipDate":{"format":"date-time","type":"string"},"status":{"description":"Order Status","enum":["placed","approved","delivered"],"type":"string"}},"type":"object","xml":{"name":"Order"}},"Pet":{"properties":{"category":{"$ref":"#/definitions/Category"},"id":{"format":"int64","type":"integer"},"name":{"example":"doggie","type":"string"},"photoUrls":{"items":{"type":"string","xml":{"name":"photoUrl"}},"type":"array","xml":{"wrapped":true}},"status":{"description":"pet status in the store","enum":["available","pending","sold"],"type":"string"},"tags":{"items":{"$ref":"#/definitions/Tag"},"type":"array","xml":{"wrapped":true}}},"required":["name","photoUrls"],"type":"object","xml":{"name":"Pet"}},"Tag":{"properties":{"id":{"format":"int64","type":"integer"},"name":{"type":"string"}},"type":"object","xml":{"name":"Tag"}},"User":{"properties":{"email":{"type":"string"},"firstName":{"type":"string"},"id":{"format":"int64","type":"integer"},"lastName":{"type":"string"},"password":{"type":"string"},"phone":{"type":"string"},"userStatus":{"description":"User Status","format":"int32","type":"integer"},"username":{"type":"string"}},"type":"object","xml":{"name":"User"}}},"securityDefinitions":{"api_key":{"type":"apiKey","in":"header","name":"api_key"},"petstore_auth":{"type":"oauth2","flow":"implicit","authorizationUrl":"https://petstore.swagger.io/oauth/authorize","scopes":{"read:pets":"read your pets","write:pets":"modify pets in your account"}}},"tags":[{"name":"pet","description":"Everything about your Pets","externalDocs":{"description":"Find out more","url":"http://swagger.io"}},{"name":"store","description":"Access to Petstore orders"},{"name":"user","description":"Operations about user","externalDocs":{"description":"Find out more about our store","url":"http://swagger.io"}}]} kin-openapi-0.124.0/openapi2conv/ 0000775 0000000 0000000 00000000000 14604223742 0016477 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi2conv/doc.go 0000664 0000000 0000000 00000000142 14604223742 0017570 0 ustar 00root root 0000000 0000000 // Package openapi2conv converts an OpenAPI v2 specification document to v3.
package openapi2conv
kin-openapi-0.124.0/openapi2conv/issue187_test.go 0000664 0000000 0000000 00000011447 14604223742 0021464 0 ustar 00root root 0000000 0000000 package openapi2conv
import (
"context"
"encoding/json"
"testing"
"github.com/invopop/yaml"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi2"
"github.com/getkin/kin-openapi/openapi3"
)
func v2v3JSON(spec2 []byte) (doc3 *openapi3.T, err error) {
var doc2 openapi2.T
if err = json.Unmarshal(spec2, &doc2); err != nil {
return
}
doc3, err = ToV3(&doc2)
return
}
func v2v3YAML(spec2 []byte) (doc3 *openapi3.T, err error) {
var doc2 openapi2.T
if err = yaml.Unmarshal(spec2, &doc2); err != nil {
return
}
doc3, err = ToV3(&doc2)
return
}
func TestIssue187(t *testing.T) {
spec := `
{
"swagger": "2.0",
"info": {
"description": "Test Golang Application",
"version": "1.0",
"title": "Test",
"contact": {
"name": "Test",
"email": "test@test.com"
}
},
"paths": {
"/me": {
"get": {
"description": "",
"operationId": "someTest",
"summary": "Some test",
"tags": ["probe"],
"produces": ["application/json"],
"responses": {
"200": {
"description": "successful operation",
"schema": {"$ref": "#/definitions/model.ProductSearchAttributeRequest"}
}
}
}
}
},
"host": "",
"basePath": "/test",
"definitions": {
"model.ProductSearchAttributeRequest": {
"type": "object",
"properties": {
"filterField": {
"type": "string"
},
"filterKey": {
"type": "string"
},
"type": {
"type": "string"
},
"values": {
"$ref": "#/definitions/model.ProductSearchAttributeValueRequest"
}
},
"title": "model.ProductSearchAttributeRequest"
},
"model.ProductSearchAttributeValueRequest": {
"type": "object",
"properties": {
"imageUrl": {
"type": "string"
},
"text": {
"type": "string"
}
},
"title": "model.ProductSearchAttributeValueRequest"
}
}
}
`
doc3, err := v2v3JSON([]byte(spec))
require.NoError(t, err)
spec3, err := json.Marshal(doc3)
require.NoError(t, err)
const expected = `{"components":{"schemas":{"model.ProductSearchAttributeRequest":{"properties":{"filterField":{"type":"string"},"filterKey":{"type":"string"},"type":{"type":"string"},"values":{"$ref":"#/components/schemas/model.ProductSearchAttributeValueRequest"}},"title":"model.ProductSearchAttributeRequest","type":"object"},"model.ProductSearchAttributeValueRequest":{"properties":{"imageUrl":{"type":"string"},"text":{"type":"string"}},"title":"model.ProductSearchAttributeValueRequest","type":"object"}}},"info":{"contact":{"email":"test@test.com","name":"Test"},"description":"Test Golang Application","title":"Test","version":"1.0"},"openapi":"3.0.3","paths":{"/me":{"get":{"operationId":"someTest","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/model.ProductSearchAttributeRequest"}}},"description":"successful operation"}},"summary":"Some test","tags":["probe"]}}}}`
require.JSONEq(t, string(spec3), expected)
err = doc3.Validate(context.Background())
require.NoError(t, err)
}
func TestIssue237(t *testing.T) {
spec := `
swagger: '2.0'
info:
version: 1.0.0
title: title
paths:
/test:
get:
parameters:
- in: body
schema:
$ref: '#/definitions/TestRef'
responses:
'200':
description: description
definitions:
TestRef:
type: object
allOf:
- $ref: '#/definitions/TestRef2'
TestRef2:
type: object
`
doc3, err := v2v3YAML([]byte(spec))
require.NoError(t, err)
spec3, err := yaml.Marshal(doc3)
require.NoError(t, err)
const expected = `components:
schemas:
TestRef:
allOf:
- $ref: '#/components/schemas/TestRef2'
type: object
TestRef2:
type: object
info:
title: title
version: 1.0.0
openapi: 3.0.3
paths:
/test:
get:
requestBody:
content:
'*/*':
schema:
$ref: '#/components/schemas/TestRef'
responses:
"200":
description: description
`
require.YAMLEq(t, string(spec3), expected)
err = doc3.Validate(context.Background())
require.NoError(t, err)
}
func TestPR449(t *testing.T) {
spec := `
swagger: '2.0'
info:
version: 1.0.0
title: title
securityDefinitions:
OAuth2Application:
type: "oauth2"
flow: "application"
tokenUrl: "example.com/oauth2/token"
`
doc3, err := v2v3YAML([]byte(spec))
require.NoError(t, err)
require.NotNil(t, doc3.Components.SecuritySchemes["OAuth2Application"].Value.Flows.ClientCredentials)
_, err = yaml.Marshal(doc3)
require.NoError(t, err)
doc2, err := FromV3(doc3)
require.NoError(t, err)
require.Equal(t, doc2.SecurityDefinitions["OAuth2Application"].Flow, "application")
}
kin-openapi-0.124.0/openapi2conv/issue440_test.go 0000664 0000000 0000000 00000002205 14604223742 0021444 0 ustar 00root root 0000000 0000000 package openapi2conv
import (
"context"
"encoding/json"
"os"
"testing"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi2"
"github.com/getkin/kin-openapi/openapi3"
)
func TestIssue440(t *testing.T) {
doc2file, err := os.Open("testdata/swagger.json")
require.NoError(t, err)
defer doc2file.Close()
var doc2 openapi2.T
err = json.NewDecoder(doc2file).Decode(&doc2)
require.NoError(t, err)
doc3, err := ToV3(&doc2)
require.NoError(t, err)
err = doc3.Validate(context.Background())
require.NoError(t, err)
require.Equal(t, openapi3.Servers{
{URL: "https://petstore.swagger.io/v2"},
{URL: "http://petstore.swagger.io/v2"},
}, doc3.Servers)
doc2.Host = "your-bot-domain.de"
doc2.Schemes = nil
doc2.BasePath = ""
doc3, err = ToV3(&doc2)
require.NoError(t, err)
err = doc3.Validate(context.Background())
require.NoError(t, err)
require.Equal(t, openapi3.Servers{
{URL: "https://your-bot-domain.de/"},
}, doc3.Servers)
doc2.Host = "https://your-bot-domain.de"
doc2.Schemes = nil
doc2.BasePath = ""
doc3, err = ToV3(&doc2)
require.Error(t, err)
require.ErrorContains(t, err, `invalid host`)
}
kin-openapi-0.124.0/openapi2conv/issue558_test.go 0000664 0000000 0000000 00000001261 14604223742 0021457 0 ustar 00root root 0000000 0000000 package openapi2conv
import (
"testing"
"github.com/invopop/yaml"
"github.com/stretchr/testify/require"
)
func TestPR558(t *testing.T) {
spec := `
swagger: '2.0'
info:
version: 1.0.0
title: title
paths:
/test:
get:
deprecated: true
parameters:
- in: body
schema:
type: object
responses:
'200':
description: description
`
doc3, err := v2v3YAML([]byte(spec))
require.NoError(t, err)
require.NotEmpty(t, doc3.Paths.Value("/test").Get.Deprecated)
_, err = yaml.Marshal(doc3)
require.NoError(t, err)
doc2, err := FromV3(doc3)
require.NoError(t, err)
require.NotEmpty(t, doc2.Paths["/test"].Get.Deprecated)
}
kin-openapi-0.124.0/openapi2conv/issue573_test.go 0000664 0000000 0000000 00000002376 14604223742 0021464 0 ustar 00root root 0000000 0000000 package openapi2conv
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestIssue573(t *testing.T) {
spec := []byte(`paths:
/ping:
get:
produces:
- application/toml
- application/xml
responses:
200:
schema:
type: object
properties:
username:
type: string
description: The user name.
post:
responses:
200:
schema:
type: object
properties:
username:
type: string
description: The user name.`)
v3, err := v2v3YAML(spec)
require.NoError(t, err)
// Make sure the response content appears for each mime-type originally
// appeared in "produces".
pingGetContent := v3.Paths.Value("/ping").Get.Responses.Value("200").Value.Content
require.Len(t, pingGetContent, 2)
require.Contains(t, pingGetContent, "application/toml")
require.Contains(t, pingGetContent, "application/xml")
// Is "produces" is not explicitly specified, default to "application/json".
pingPostContent := v3.Paths.Value("/ping").Post.Responses.Value("200").Value.Content
require.Len(t, pingPostContent, 1)
require.Contains(t, pingPostContent, "application/json")
}
kin-openapi-0.124.0/openapi2conv/issue847_test.go 0000664 0000000 0000000 00000001534 14604223742 0021463 0 ustar 00root root 0000000 0000000 package openapi2conv
import (
"context"
"testing"
"github.com/stretchr/testify/require"
)
func TestIssue847(t *testing.T) {
v2 := []byte(`
swagger: '2.0'
info:
version: '1.10'
title: title
paths:
"/ping":
post:
consumes:
- multipart/form-data
parameters:
- name: file
in: formData
description: file
required: true
type: file
responses:
'200':
description: OK
`)
v3, err := v2v3YAML(v2)
require.NoError(t, err)
err = v3.Validate(context.Background())
require.NoError(t, err)
require.Equal(t, []string{"file"}, v3.Paths.Value("/ping").Post.RequestBody.Value.Content["multipart/form-data"].Schema.Value.Required)
require.Nil(t, v3.Paths.Value("/ping").Post.RequestBody.Value.Content["multipart/form-data"].Schema.Value.Properties["file"].Value.Required)
}
kin-openapi-0.124.0/openapi2conv/openapi2_conv.go 0000664 0000000 0000000 00000106557 14604223742 0021606 0 ustar 00root root 0000000 0000000 package openapi2conv
import (
"errors"
"fmt"
"net/url"
"sort"
"strings"
"github.com/getkin/kin-openapi/openapi2"
"github.com/getkin/kin-openapi/openapi3"
)
// ToV3 converts an OpenAPIv2 spec to an OpenAPIv3 spec
func ToV3(doc2 *openapi2.T) (*openapi3.T, error) {
return ToV3WithLoader(doc2, openapi3.NewLoader(), nil)
}
func ToV3WithLoader(doc2 *openapi2.T, loader *openapi3.Loader, location *url.URL) (*openapi3.T, error) {
doc3 := &openapi3.T{
OpenAPI: "3.0.3",
Info: &doc2.Info,
Components: &openapi3.Components{},
Tags: doc2.Tags,
Extensions: stripNonExtensions(doc2.Extensions),
ExternalDocs: doc2.ExternalDocs,
}
if host := doc2.Host; host != "" {
if strings.Contains(host, "/") {
err := fmt.Errorf("invalid host %q. This MUST be the host only and does not include the scheme nor sub-paths.", host)
return nil, err
}
schemes := doc2.Schemes
if len(schemes) == 0 {
schemes = []string{"https"}
}
basePath := doc2.BasePath
if basePath == "" {
basePath = "/"
}
for _, scheme := range schemes {
u := url.URL{
Scheme: scheme,
Host: host,
Path: basePath,
}
doc3.AddServer(&openapi3.Server{URL: u.String()})
}
}
doc3.Components.Schemas = make(map[string]*openapi3.SchemaRef)
if parameters := doc2.Parameters; len(parameters) != 0 {
doc3.Components.Parameters = make(map[string]*openapi3.ParameterRef)
doc3.Components.RequestBodies = make(map[string]*openapi3.RequestBodyRef)
for k, parameter := range parameters {
v3Parameter, v3RequestBody, v3SchemaMap, err := ToV3Parameter(doc3.Components, parameter, doc2.Consumes)
switch {
case err != nil:
return nil, err
case v3RequestBody != nil:
doc3.Components.RequestBodies[k] = v3RequestBody
case v3SchemaMap != nil:
for _, v3Schema := range v3SchemaMap {
doc3.Components.Schemas[k] = v3Schema
}
default:
doc3.Components.Parameters[k] = v3Parameter
}
}
}
if paths := doc2.Paths; len(paths) != 0 {
doc3.Paths = openapi3.NewPathsWithCapacity(len(paths))
for path, pathItem := range paths {
r, err := ToV3PathItem(doc2, doc3.Components, pathItem, doc2.Consumes)
if err != nil {
return nil, err
}
doc3.Paths.Set(path, r)
}
}
if responses := doc2.Responses; len(responses) != 0 {
doc3.Components.Responses = make(openapi3.ResponseBodies, len(responses))
for k, response := range responses {
r, err := ToV3Response(response, doc2.Produces)
if err != nil {
return nil, err
}
doc3.Components.Responses[k] = r
}
}
for key, schema := range ToV3Schemas(doc2.Definitions) {
doc3.Components.Schemas[key] = schema
}
if m := doc2.SecurityDefinitions; len(m) != 0 {
doc3SecuritySchemes := make(map[string]*openapi3.SecuritySchemeRef)
for k, v := range m {
r, err := ToV3SecurityScheme(v)
if err != nil {
return nil, err
}
doc3SecuritySchemes[k] = r
}
doc3.Components.SecuritySchemes = doc3SecuritySchemes
}
doc3.Security = ToV3SecurityRequirements(doc2.Security)
if err := loader.ResolveRefsIn(doc3, location); err != nil {
return nil, err
}
return doc3, nil
}
func ToV3PathItem(doc2 *openapi2.T, components *openapi3.Components, pathItem *openapi2.PathItem, consumes []string) (*openapi3.PathItem, error) {
doc3 := &openapi3.PathItem{
Extensions: stripNonExtensions(pathItem.Extensions),
}
for method, operation := range pathItem.Operations() {
doc3Operation, err := ToV3Operation(doc2, components, pathItem, operation, consumes)
if err != nil {
return nil, err
}
doc3.SetOperation(method, doc3Operation)
}
for _, parameter := range pathItem.Parameters {
v3Parameter, v3RequestBody, v3Schema, err := ToV3Parameter(components, parameter, consumes)
switch {
case err != nil:
return nil, err
case v3RequestBody != nil:
return nil, errors.New("pathItem must not have a body parameter")
case v3Schema != nil:
return nil, errors.New("pathItem must not have a schema parameter")
default:
doc3.Parameters = append(doc3.Parameters, v3Parameter)
}
}
return doc3, nil
}
func ToV3Operation(doc2 *openapi2.T, components *openapi3.Components, pathItem *openapi2.PathItem, operation *openapi2.Operation, consumes []string) (*openapi3.Operation, error) {
if operation == nil {
return nil, nil
}
doc3 := &openapi3.Operation{
OperationID: operation.OperationID,
Summary: operation.Summary,
Description: operation.Description,
Deprecated: operation.Deprecated,
Tags: operation.Tags,
Extensions: stripNonExtensions(operation.Extensions),
}
if v := operation.Security; v != nil {
doc3Security := ToV3SecurityRequirements(*v)
doc3.Security = &doc3Security
}
if len(operation.Consumes) > 0 {
consumes = operation.Consumes
}
var reqBodies []*openapi3.RequestBodyRef
formDataSchemas := make(map[string]*openapi3.SchemaRef)
for _, parameter := range operation.Parameters {
v3Parameter, v3RequestBody, v3SchemaMap, err := ToV3Parameter(components, parameter, consumes)
switch {
case err != nil:
return nil, err
case v3RequestBody != nil:
reqBodies = append(reqBodies, v3RequestBody)
case v3SchemaMap != nil:
for key, v3Schema := range v3SchemaMap {
formDataSchemas[key] = v3Schema
}
default:
doc3.Parameters = append(doc3.Parameters, v3Parameter)
}
}
var err error
if doc3.RequestBody, err = onlyOneReqBodyParam(reqBodies, formDataSchemas, components, consumes); err != nil {
return nil, err
}
if responses := operation.Responses; responses != nil {
doc3.Responses = openapi3.NewResponsesWithCapacity(len(responses))
for k, response := range responses {
responseRef3, err := ToV3Response(response, operation.Produces)
if err != nil {
return nil, err
}
doc3.Responses.Set(k, responseRef3)
}
}
return doc3, nil
}
func getParameterNameFromOldRef(ref string) string {
cleanPath := strings.TrimPrefix(ref, "#/parameters/")
pathSections := strings.SplitN(cleanPath, "/", 1)
return pathSections[0]
}
func ToV3Parameter(components *openapi3.Components, parameter *openapi2.Parameter, consumes []string) (*openapi3.ParameterRef, *openapi3.RequestBodyRef, map[string]*openapi3.SchemaRef, error) {
if ref := parameter.Ref; ref != "" {
if strings.HasPrefix(ref, "#/parameters/") {
name := getParameterNameFromOldRef(ref)
if _, ok := components.RequestBodies[name]; ok {
v3Ref := strings.Replace(ref, "#/parameters/", "#/components/requestBodies/", 1)
return nil, &openapi3.RequestBodyRef{Ref: v3Ref}, nil, nil
} else if schema, ok := components.Schemas[name]; ok {
schemaRefMap := make(map[string]*openapi3.SchemaRef)
if val, ok := schema.Value.Extensions["x-formData-name"]; ok {
name = val.(string)
}
v3Ref := strings.Replace(ref, "#/parameters/", "#/components/schemas/", 1)
schemaRefMap[name] = &openapi3.SchemaRef{Ref: v3Ref}
return nil, nil, schemaRefMap, nil
}
}
return &openapi3.ParameterRef{Ref: ToV3Ref(ref)}, nil, nil, nil
}
switch parameter.In {
case "body":
result := &openapi3.RequestBody{
Description: parameter.Description,
Required: parameter.Required,
Extensions: stripNonExtensions(parameter.Extensions),
}
if parameter.Name != "" {
if result.Extensions == nil {
result.Extensions = make(map[string]interface{}, 1)
}
result.Extensions["x-originalParamName"] = parameter.Name
}
if schemaRef := parameter.Schema; schemaRef != nil {
result.WithSchemaRef(ToV3SchemaRef(schemaRef), consumes)
}
return nil, &openapi3.RequestBodyRef{Value: result}, nil, nil
case "formData":
format, typ := parameter.Format, parameter.Type
if typ.Is("file") {
format, typ = "binary", &openapi3.Types{"string"}
}
if parameter.Extensions == nil {
parameter.Extensions = make(map[string]interface{}, 1)
}
parameter.Extensions["x-formData-name"] = parameter.Name
var required []string
if parameter.Required {
required = []string{parameter.Name}
}
schemaRef := &openapi3.SchemaRef{Value: &openapi3.Schema{
Description: parameter.Description,
Type: typ,
Extensions: stripNonExtensions(parameter.Extensions),
Format: format,
Enum: parameter.Enum,
Min: parameter.Minimum,
Max: parameter.Maximum,
ExclusiveMin: parameter.ExclusiveMin,
ExclusiveMax: parameter.ExclusiveMax,
MinLength: parameter.MinLength,
MaxLength: parameter.MaxLength,
Default: parameter.Default,
Items: parameter.Items,
MinItems: parameter.MinItems,
MaxItems: parameter.MaxItems,
Pattern: parameter.Pattern,
AllowEmptyValue: parameter.AllowEmptyValue,
UniqueItems: parameter.UniqueItems,
MultipleOf: parameter.MultipleOf,
Required: required,
}}
schemaRefMap := make(map[string]*openapi3.SchemaRef, 1)
schemaRefMap[parameter.Name] = schemaRef
return nil, nil, schemaRefMap, nil
default:
required := parameter.Required
if parameter.In == openapi3.ParameterInPath {
required = true
}
var schemaRefRef string
if schemaRef := parameter.Schema; schemaRef != nil && schemaRef.Ref != "" {
schemaRefRef = schemaRef.Ref
}
result := &openapi3.Parameter{
In: parameter.In,
Name: parameter.Name,
Description: parameter.Description,
Required: required,
Extensions: stripNonExtensions(parameter.Extensions),
Schema: ToV3SchemaRef(&openapi3.SchemaRef{Value: &openapi3.Schema{
Type: parameter.Type,
Format: parameter.Format,
Enum: parameter.Enum,
Min: parameter.Minimum,
Max: parameter.Maximum,
ExclusiveMin: parameter.ExclusiveMin,
ExclusiveMax: parameter.ExclusiveMax,
MinLength: parameter.MinLength,
MaxLength: parameter.MaxLength,
Default: parameter.Default,
Items: parameter.Items,
MinItems: parameter.MinItems,
MaxItems: parameter.MaxItems,
Pattern: parameter.Pattern,
AllowEmptyValue: parameter.AllowEmptyValue,
UniqueItems: parameter.UniqueItems,
MultipleOf: parameter.MultipleOf,
},
Ref: schemaRefRef,
}),
}
return &openapi3.ParameterRef{Value: result}, nil, nil, nil
}
}
func formDataBody(bodies map[string]*openapi3.SchemaRef, reqs map[string]bool, consumes []string) *openapi3.RequestBodyRef {
if len(bodies) != len(reqs) {
panic(`request bodies and them being required must match`)
}
requireds := make([]string, 0, len(reqs))
for propName, req := range reqs {
if _, ok := bodies[propName]; !ok {
panic(`request bodies and them being required must match`)
}
if req {
requireds = append(requireds, propName)
}
}
for s, ref := range bodies {
if ref.Value != nil && len(ref.Value.Required) > 0 {
ref.Value.Required = nil
bodies[s] = ref
}
}
schema := &openapi3.Schema{
Type: &openapi3.Types{"object"},
Properties: ToV3Schemas(bodies),
Required: requireds,
}
return &openapi3.RequestBodyRef{
Value: openapi3.NewRequestBody().WithSchema(schema, consumes),
}
}
func getParameterNameFromNewRef(ref string) string {
cleanPath := strings.TrimPrefix(ref, "#/components/schemas/")
pathSections := strings.SplitN(cleanPath, "/", 1)
return pathSections[0]
}
func onlyOneReqBodyParam(bodies []*openapi3.RequestBodyRef, formDataSchemas map[string]*openapi3.SchemaRef, components *openapi3.Components, consumes []string) (*openapi3.RequestBodyRef, error) {
if len(bodies) > 1 {
return nil, errors.New("multiple body parameters cannot exist for the same operation")
}
if len(bodies) != 0 && len(formDataSchemas) != 0 {
return nil, errors.New("body and form parameters cannot exist together for the same operation")
}
for _, requestBodyRef := range bodies {
return requestBodyRef, nil
}
if len(formDataSchemas) > 0 {
formDataParams := make(map[string]*openapi3.SchemaRef, len(formDataSchemas))
formDataReqs := make(map[string]bool, len(formDataSchemas))
for formDataName, formDataSchema := range formDataSchemas {
if formDataSchema.Ref != "" {
name := getParameterNameFromNewRef(formDataSchema.Ref)
if schema := components.Schemas[name]; schema != nil && schema.Value != nil {
if tempName, ok := schema.Value.Extensions["x-formData-name"]; ok {
name = tempName.(string)
}
formDataParams[name] = formDataSchema
formDataReqs[name] = false
for _, req := range schema.Value.Required {
if name == req {
formDataReqs[name] = true
}
}
}
} else if formDataSchema.Value != nil {
formDataParams[formDataName] = formDataSchema
formDataReqs[formDataName] = false
for _, req := range formDataSchema.Value.Required {
if formDataName == req {
formDataReqs[formDataName] = true
}
}
}
}
return formDataBody(formDataParams, formDataReqs, consumes), nil
}
return nil, nil
}
func ToV3Response(response *openapi2.Response, produces []string) (*openapi3.ResponseRef, error) {
if ref := response.Ref; ref != "" {
return &openapi3.ResponseRef{Ref: ToV3Ref(ref)}, nil
}
result := &openapi3.Response{
Description: &response.Description,
Extensions: stripNonExtensions(response.Extensions),
}
// Default to "application/json" if "produces" is not specified.
if len(produces) == 0 {
produces = []string{"application/json"}
}
if schemaRef := response.Schema; schemaRef != nil {
schema := ToV3SchemaRef(schemaRef)
result.Content = make(openapi3.Content, len(produces))
for _, mime := range produces {
result.Content[mime] = openapi3.NewMediaType().WithSchemaRef(schema)
}
}
if headers := response.Headers; len(headers) > 0 {
result.Headers = ToV3Headers(headers)
}
return &openapi3.ResponseRef{Value: result}, nil
}
func ToV3Headers(defs map[string]*openapi2.Header) openapi3.Headers {
headers := make(openapi3.Headers, len(defs))
for name, header := range defs {
header.In = ""
header.Name = ""
if ref := header.Ref; ref != "" {
headers[name] = &openapi3.HeaderRef{Ref: ToV3Ref(ref)}
} else {
parameter, _, _, _ := ToV3Parameter(nil, &header.Parameter, nil)
headers[name] = &openapi3.HeaderRef{Value: &openapi3.Header{
Parameter: *parameter.Value,
}}
}
}
return headers
}
func ToV3Schemas(defs map[string]*openapi3.SchemaRef) map[string]*openapi3.SchemaRef {
schemas := make(map[string]*openapi3.SchemaRef, len(defs))
for name, schema := range defs {
schemas[name] = ToV3SchemaRef(schema)
}
return schemas
}
func ToV3SchemaRef(schema *openapi3.SchemaRef) *openapi3.SchemaRef {
if ref := schema.Ref; ref != "" {
return &openapi3.SchemaRef{Ref: ToV3Ref(ref)}
}
if schema.Value == nil {
return schema
}
if schema.Value.Items != nil {
schema.Value.Items = ToV3SchemaRef(schema.Value.Items)
}
for k, v := range schema.Value.Properties {
schema.Value.Properties[k] = ToV3SchemaRef(v)
}
if v := schema.Value.AdditionalProperties.Schema; v != nil {
schema.Value.AdditionalProperties.Schema = ToV3SchemaRef(v)
}
for i, v := range schema.Value.AllOf {
schema.Value.AllOf[i] = ToV3SchemaRef(v)
}
if val, ok := schema.Value.Extensions["x-nullable"]; ok {
schema.Value.Nullable, _ = val.(bool)
delete(schema.Value.Extensions, "x-nullable")
}
return schema
}
var ref2To3 = map[string]string{
"#/definitions/": "#/components/schemas/",
"#/responses/": "#/components/responses/",
"#/parameters/": "#/components/parameters/",
}
func ToV3Ref(ref string) string {
for old, new := range ref2To3 {
if strings.HasPrefix(ref, old) {
ref = strings.Replace(ref, old, new, 1)
}
}
return ref
}
func FromV3Ref(ref string) string {
for new, old := range ref2To3 {
if strings.HasPrefix(ref, old) {
ref = strings.Replace(ref, old, new, 1)
} else if strings.HasPrefix(ref, "#/components/requestBodies/") {
ref = strings.Replace(ref, "#/components/requestBodies/", "#/parameters/", 1)
}
}
return ref
}
func ToV3SecurityRequirements(requirements openapi2.SecurityRequirements) openapi3.SecurityRequirements {
if requirements == nil {
return nil
}
result := make(openapi3.SecurityRequirements, len(requirements))
for i, item := range requirements {
result[i] = item
}
return result
}
func ToV3SecurityScheme(securityScheme *openapi2.SecurityScheme) (*openapi3.SecuritySchemeRef, error) {
if securityScheme == nil {
return nil, nil
}
result := &openapi3.SecurityScheme{
Description: securityScheme.Description,
Extensions: stripNonExtensions(securityScheme.Extensions),
}
switch securityScheme.Type {
case "basic":
result.Type = "http"
result.Scheme = "basic"
case "apiKey":
result.Type = "apiKey"
result.In = securityScheme.In
result.Name = securityScheme.Name
case "oauth2":
result.Type = "oauth2"
flows := &openapi3.OAuthFlows{}
result.Flows = flows
scopesMap := make(map[string]string)
for scope, desc := range securityScheme.Scopes {
scopesMap[scope] = desc
}
flow := &openapi3.OAuthFlow{
AuthorizationURL: securityScheme.AuthorizationURL,
TokenURL: securityScheme.TokenURL,
Scopes: scopesMap,
}
switch securityScheme.Flow {
case "implicit":
flows.Implicit = flow
case "accessCode":
flows.AuthorizationCode = flow
case "password":
flows.Password = flow
case "application":
flows.ClientCredentials = flow
default:
return nil, fmt.Errorf("unsupported flow %q", securityScheme.Flow)
}
}
return &openapi3.SecuritySchemeRef{
Ref: ToV3Ref(securityScheme.Ref),
Value: result,
}, nil
}
// FromV3 converts an OpenAPIv3 spec to an OpenAPIv2 spec
func FromV3(doc3 *openapi3.T) (*openapi2.T, error) {
doc2Responses, err := FromV3Responses(doc3.Components.Responses, doc3.Components)
if err != nil {
return nil, err
}
schemas, parameters := FromV3Schemas(doc3.Components.Schemas, doc3.Components)
doc2 := &openapi2.T{
Swagger: "2.0",
Info: *doc3.Info,
Definitions: schemas,
Parameters: parameters,
Responses: doc2Responses,
Tags: doc3.Tags,
Extensions: stripNonExtensions(doc3.Extensions),
ExternalDocs: doc3.ExternalDocs,
}
isHTTPS := false
isHTTP := false
servers := doc3.Servers
for i, server := range servers {
parsedURL, err := url.Parse(server.URL)
if err == nil {
// See which schemes seem to be supported
if parsedURL.Scheme == "https" {
isHTTPS = true
} else if parsedURL.Scheme == "http" {
isHTTP = true
}
// The first server is assumed to provide the base path
if i == 0 {
doc2.Host = parsedURL.Host
doc2.BasePath = parsedURL.Path
}
}
}
if isHTTPS {
doc2.Schemes = append(doc2.Schemes, "https")
}
if isHTTP {
doc2.Schemes = append(doc2.Schemes, "http")
}
for path, pathItem := range doc3.Paths.Map() {
if pathItem == nil {
continue
}
doc2.AddOperation(path, "GET", nil)
addPathExtensions(doc2, path, stripNonExtensions(pathItem.Extensions))
for method, operation := range pathItem.Operations() {
if operation == nil {
continue
}
doc2Operation, err := FromV3Operation(doc3, operation)
if err != nil {
return nil, err
}
doc2.AddOperation(path, method, doc2Operation)
}
params := openapi2.Parameters{}
for _, param := range pathItem.Parameters {
p, err := FromV3Parameter(param, doc3.Components)
if err != nil {
return nil, err
}
params = append(params, p)
}
sort.Sort(params)
doc2.Paths[path].Parameters = params
}
for name, param := range doc3.Components.Parameters {
if doc2.Parameters[name], err = FromV3Parameter(param, doc3.Components); err != nil {
return nil, err
}
}
for name, requestBodyRef := range doc3.Components.RequestBodies {
bodyOrRefParameters, formDataParameters, consumes, err := fromV3RequestBodies(name, requestBodyRef, doc3.Components)
if err != nil {
return nil, err
}
if len(formDataParameters) != 0 {
for _, param := range formDataParameters {
doc2.Parameters[param.Name] = param
}
} else if len(bodyOrRefParameters) != 0 {
for _, param := range bodyOrRefParameters {
doc2.Parameters[name] = param
}
}
if len(consumes) != 0 {
doc2.Consumes = consumesToArray(consumes)
}
}
if m := doc3.Components.SecuritySchemes; m != nil {
doc2SecuritySchemes := make(map[string]*openapi2.SecurityScheme)
for id, securityScheme := range m {
v, err := FromV3SecurityScheme(doc3, securityScheme)
if err != nil {
return nil, err
}
doc2SecuritySchemes[id] = v
}
doc2.SecurityDefinitions = doc2SecuritySchemes
}
doc2.Security = FromV3SecurityRequirements(doc3.Security)
return doc2, nil
}
func consumesToArray(consumes map[string]struct{}) []string {
consumesArr := make([]string, 0, len(consumes))
for key := range consumes {
consumesArr = append(consumesArr, key)
}
sort.Strings(consumesArr)
return consumesArr
}
func fromV3RequestBodies(name string, requestBodyRef *openapi3.RequestBodyRef, components *openapi3.Components) (
bodyOrRefParameters openapi2.Parameters,
formParameters openapi2.Parameters,
consumes map[string]struct{},
err error,
) {
if ref := requestBodyRef.Ref; ref != "" {
bodyOrRefParameters = append(bodyOrRefParameters, &openapi2.Parameter{Ref: FromV3Ref(ref)})
return
}
// Only select one formData or request body for an individual requestBody as OpenAPI 2 does not support multiples
if requestBodyRef.Value != nil {
for contentType, mediaType := range requestBodyRef.Value.Content {
if consumes == nil {
consumes = make(map[string]struct{})
}
consumes[contentType] = struct{}{}
if contentType == "application/x-www-form-urlencoded" || contentType == "multipart/form-data" {
formParameters = FromV3RequestBodyFormData(mediaType)
continue
}
paramName := name
if originalName, ok := requestBodyRef.Value.Extensions["x-originalParamName"]; ok {
paramName = originalName.(string)
}
var r *openapi2.Parameter
if r, err = FromV3RequestBody(paramName, requestBodyRef, mediaType, components); err != nil {
return
}
bodyOrRefParameters = append(bodyOrRefParameters, r)
}
}
return
}
func FromV3Schemas(schemas map[string]*openapi3.SchemaRef, components *openapi3.Components) (map[string]*openapi3.SchemaRef, map[string]*openapi2.Parameter) {
v2Defs := make(map[string]*openapi3.SchemaRef)
v2Params := make(map[string]*openapi2.Parameter)
for name, schema := range schemas {
schemaConv, parameterConv := FromV3SchemaRef(schema, components)
if schemaConv != nil {
v2Defs[name] = schemaConv
} else if parameterConv != nil {
if parameterConv.Name == "" {
parameterConv.Name = name
}
v2Params[name] = parameterConv
}
}
return v2Defs, v2Params
}
func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components) (*openapi3.SchemaRef, *openapi2.Parameter) {
if ref := schema.Ref; ref != "" {
name := getParameterNameFromNewRef(ref)
if val, ok := components.Schemas[name]; ok {
if val.Value.Format == "binary" {
v2Ref := strings.Replace(ref, "#/components/schemas/", "#/parameters/", 1)
return nil, &openapi2.Parameter{Ref: v2Ref}
}
}
return &openapi3.SchemaRef{Ref: FromV3Ref(ref)}, nil
}
if schema.Value == nil {
return schema, nil
}
if schema.Value != nil {
if schema.Value.Type.Is("string") && schema.Value.Format == "binary" {
paramType := &openapi3.Types{"file"}
required := false
value, _ := schema.Value.Extensions["x-formData-name"]
originalName, _ := value.(string)
for _, prop := range schema.Value.Required {
if originalName == prop {
required = true
break
}
}
return nil, &openapi2.Parameter{
In: "formData",
Name: originalName,
Description: schema.Value.Description,
Type: paramType,
Enum: schema.Value.Enum,
Minimum: schema.Value.Min,
Maximum: schema.Value.Max,
ExclusiveMin: schema.Value.ExclusiveMin,
ExclusiveMax: schema.Value.ExclusiveMax,
MinLength: schema.Value.MinLength,
MaxLength: schema.Value.MaxLength,
Default: schema.Value.Default,
Items: schema.Value.Items,
MinItems: schema.Value.MinItems,
MaxItems: schema.Value.MaxItems,
AllowEmptyValue: schema.Value.AllowEmptyValue,
UniqueItems: schema.Value.UniqueItems,
MultipleOf: schema.Value.MultipleOf,
Extensions: stripNonExtensions(schema.Value.Extensions),
Required: required,
}
}
}
if v := schema.Value.Items; v != nil {
schema.Value.Items, _ = FromV3SchemaRef(v, components)
}
keys := make([]string, 0, len(schema.Value.Properties))
for k := range schema.Value.Properties {
keys = append(keys, k)
}
sort.Strings(keys)
for _, key := range keys {
schema.Value.Properties[key], _ = FromV3SchemaRef(schema.Value.Properties[key], components)
}
if v := schema.Value.AdditionalProperties.Schema; v != nil {
schema.Value.AdditionalProperties.Schema, _ = FromV3SchemaRef(v, components)
}
for i, v := range schema.Value.AllOf {
schema.Value.AllOf[i], _ = FromV3SchemaRef(v, components)
}
if schema.Value.PermitsNull() {
schema.Value.Nullable = false
if schema.Value.Extensions == nil {
schema.Value.Extensions = make(map[string]interface{})
}
schema.Value.Extensions["x-nullable"] = true
}
return schema, nil
}
func FromV3SecurityRequirements(requirements openapi3.SecurityRequirements) openapi2.SecurityRequirements {
if requirements == nil {
return nil
}
result := make([]map[string][]string, 0, len(requirements))
for _, item := range requirements {
result = append(result, item)
}
return result
}
func FromV3PathItem(doc3 *openapi3.T, pathItem *openapi3.PathItem) (*openapi2.PathItem, error) {
result := &openapi2.PathItem{
Extensions: stripNonExtensions(pathItem.Extensions),
}
for method, operation := range pathItem.Operations() {
r, err := FromV3Operation(doc3, operation)
if err != nil {
return nil, err
}
result.SetOperation(method, r)
}
for _, parameter := range pathItem.Parameters {
p, err := FromV3Parameter(parameter, doc3.Components)
if err != nil {
return nil, err
}
result.Parameters = append(result.Parameters, p)
}
return result, nil
}
func findNameForRequestBody(operation *openapi3.Operation) string {
nameSearch:
for _, name := range attemptedBodyParameterNames {
for _, parameterRef := range operation.Parameters {
parameter := parameterRef.Value
if parameter != nil && parameter.Name == name {
continue nameSearch
}
}
return name
}
return ""
}
func FromV3RequestBodyFormData(mediaType *openapi3.MediaType) openapi2.Parameters {
parameters := openapi2.Parameters{}
for propName, schemaRef := range mediaType.Schema.Value.Properties {
if ref := schemaRef.Ref; ref != "" {
v2Ref := strings.Replace(ref, "#/components/schemas/", "#/parameters/", 1)
parameters = append(parameters, &openapi2.Parameter{Ref: v2Ref})
continue
}
val := schemaRef.Value
typ := val.Type
if val.Format == "binary" {
typ = &openapi3.Types{"file"}
}
required := false
for _, name := range val.Required {
if name == propName {
required = true
break
}
}
parameter := &openapi2.Parameter{
Name: propName,
Description: val.Description,
Type: typ,
In: "formData",
Extensions: stripNonExtensions(val.Extensions),
Enum: val.Enum,
ExclusiveMin: val.ExclusiveMin,
ExclusiveMax: val.ExclusiveMax,
MinLength: val.MinLength,
MaxLength: val.MaxLength,
Default: val.Default,
Items: val.Items,
MinItems: val.MinItems,
MaxItems: val.MaxItems,
Maximum: val.Max,
Minimum: val.Min,
Pattern: val.Pattern,
// CollectionFormat: val.CollectionFormat,
// Format: val.Format,
AllowEmptyValue: val.AllowEmptyValue,
Required: required,
UniqueItems: val.UniqueItems,
MultipleOf: val.MultipleOf,
}
parameters = append(parameters, parameter)
}
return parameters
}
func FromV3Operation(doc3 *openapi3.T, operation *openapi3.Operation) (*openapi2.Operation, error) {
if operation == nil {
return nil, nil
}
result := &openapi2.Operation{
OperationID: operation.OperationID,
Summary: operation.Summary,
Description: operation.Description,
Deprecated: operation.Deprecated,
Tags: operation.Tags,
Extensions: stripNonExtensions(operation.Extensions),
}
if v := operation.Security; v != nil {
resultSecurity := FromV3SecurityRequirements(*v)
result.Security = &resultSecurity
}
for _, parameter := range operation.Parameters {
r, err := FromV3Parameter(parameter, doc3.Components)
if err != nil {
return nil, err
}
result.Parameters = append(result.Parameters, r)
}
if v := operation.RequestBody; v != nil {
// Find parameter name that we can use for the body
name := findNameForRequestBody(operation)
if name == "" {
return nil, errors.New("could not find a name for request body")
}
bodyOrRefParameters, formDataParameters, consumes, err := fromV3RequestBodies(name, v, doc3.Components)
if err != nil {
return nil, err
}
if len(formDataParameters) != 0 {
result.Parameters = append(result.Parameters, formDataParameters...)
} else if len(bodyOrRefParameters) != 0 {
for _, param := range bodyOrRefParameters {
result.Parameters = append(result.Parameters, param)
break // add a single request body
}
}
if len(consumes) != 0 {
result.Consumes = consumesToArray(consumes)
}
}
sort.Sort(result.Parameters)
if responses := operation.Responses; responses != nil {
resultResponses, err := FromV3Responses(responses.Map(), doc3.Components)
if err != nil {
return nil, err
}
result.Responses = resultResponses
}
return result, nil
}
func FromV3RequestBody(name string, requestBodyRef *openapi3.RequestBodyRef, mediaType *openapi3.MediaType, components *openapi3.Components) (*openapi2.Parameter, error) {
requestBody := requestBodyRef.Value
result := &openapi2.Parameter{
In: "body",
Name: name,
Description: requestBody.Description,
Required: requestBody.Required,
Extensions: stripNonExtensions(requestBody.Extensions),
}
if mediaType != nil {
result.Schema, _ = FromV3SchemaRef(mediaType.Schema, components)
}
return result, nil
}
func FromV3Parameter(ref *openapi3.ParameterRef, components *openapi3.Components) (*openapi2.Parameter, error) {
if ref := ref.Ref; ref != "" {
return &openapi2.Parameter{Ref: FromV3Ref(ref)}, nil
}
parameter := ref.Value
if parameter == nil {
return nil, nil
}
result := &openapi2.Parameter{
Description: parameter.Description,
In: parameter.In,
Name: parameter.Name,
Required: parameter.Required,
Extensions: stripNonExtensions(parameter.Extensions),
}
if schemaRef := parameter.Schema; schemaRef != nil {
schemaRef, _ = FromV3SchemaRef(schemaRef, components)
if ref := schemaRef.Ref; ref != "" {
result.Schema = &openapi3.SchemaRef{Ref: FromV3Ref(ref)}
return result, nil
}
schema := schemaRef.Value
result.Type = schema.Type
result.Format = schema.Format
result.Enum = schema.Enum
result.Minimum = schema.Min
result.Maximum = schema.Max
result.ExclusiveMin = schema.ExclusiveMin
result.ExclusiveMax = schema.ExclusiveMax
result.MinLength = schema.MinLength
result.MaxLength = schema.MaxLength
result.Pattern = schema.Pattern
result.Default = schema.Default
result.Items = schema.Items
result.MinItems = schema.MinItems
result.MaxItems = schema.MaxItems
result.AllowEmptyValue = schema.AllowEmptyValue
// result.CollectionFormat = schema.CollectionFormat
result.UniqueItems = schema.UniqueItems
result.MultipleOf = schema.MultipleOf
}
return result, nil
}
func FromV3Responses(responses map[string]*openapi3.ResponseRef, components *openapi3.Components) (map[string]*openapi2.Response, error) {
v2Responses := make(map[string]*openapi2.Response, len(responses))
for k, response := range responses {
r, err := FromV3Response(response, components)
if err != nil {
return nil, err
}
v2Responses[k] = r
}
return v2Responses, nil
}
func FromV3Response(ref *openapi3.ResponseRef, components *openapi3.Components) (*openapi2.Response, error) {
if ref := ref.Ref; ref != "" {
return &openapi2.Response{Ref: FromV3Ref(ref)}, nil
}
response := ref.Value
if response == nil {
return nil, nil
}
description := ""
if desc := response.Description; desc != nil {
description = *desc
}
result := &openapi2.Response{
Description: description,
Extensions: stripNonExtensions(response.Extensions),
}
if content := response.Content; content != nil {
if ct := content["application/json"]; ct != nil {
result.Schema, _ = FromV3SchemaRef(ct.Schema, components)
}
}
if headers := response.Headers; len(headers) > 0 {
var err error
if result.Headers, err = FromV3Headers(headers, components); err != nil {
return nil, err
}
}
return result, nil
}
func FromV3Headers(defs openapi3.Headers, components *openapi3.Components) (map[string]*openapi2.Header, error) {
headers := make(map[string]*openapi2.Header, len(defs))
for name, header := range defs {
ref := openapi3.ParameterRef{Ref: header.Ref, Value: &header.Value.Parameter}
parameter, err := FromV3Parameter(&ref, components)
if err != nil {
return nil, err
}
parameter.In = ""
parameter.Name = ""
headers[name] = &openapi2.Header{Parameter: *parameter}
}
return headers, nil
}
func FromV3SecurityScheme(doc3 *openapi3.T, ref *openapi3.SecuritySchemeRef) (*openapi2.SecurityScheme, error) {
securityScheme := ref.Value
if securityScheme == nil {
return nil, nil
}
result := &openapi2.SecurityScheme{
Ref: FromV3Ref(ref.Ref),
Description: securityScheme.Description,
Extensions: stripNonExtensions(securityScheme.Extensions),
}
switch securityScheme.Type {
case "http":
switch securityScheme.Scheme {
case "basic":
result.Type = "basic"
default:
result.Type = "apiKey"
result.In = "header"
result.Name = "Authorization"
}
case "apiKey":
result.Type = "apiKey"
result.In = securityScheme.In
result.Name = securityScheme.Name
case "oauth2":
result.Type = "oauth2"
flows := securityScheme.Flows
if flows != nil {
var flow *openapi3.OAuthFlow
// TODO: Is this the right priority? What if multiple defined?
switch {
case flows.Implicit != nil:
result.Flow = "implicit"
flow = flows.Implicit
result.AuthorizationURL = flow.AuthorizationURL
case flows.AuthorizationCode != nil:
result.Flow = "accessCode"
flow = flows.AuthorizationCode
result.AuthorizationURL = flow.AuthorizationURL
result.TokenURL = flow.TokenURL
case flows.Password != nil:
result.Flow = "password"
flow = flows.Password
result.TokenURL = flow.TokenURL
case flows.ClientCredentials != nil:
result.Flow = "application"
flow = flows.ClientCredentials
result.TokenURL = flow.TokenURL
default:
return nil, nil
}
result.Scopes = make(map[string]string, len(flow.Scopes))
for scope, desc := range flow.Scopes {
result.Scopes[scope] = desc
}
}
default:
return nil, fmt.Errorf("unsupported security scheme type %q", securityScheme.Type)
}
return result, nil
}
var attemptedBodyParameterNames = []string{
"body",
"requestBody",
}
// stripNonExtensions removes invalid extensions: those not prefixed by "x-" and returns them
func stripNonExtensions(extensions map[string]interface{}) map[string]interface{} {
for extName := range extensions {
if !strings.HasPrefix(extName, "x-") {
delete(extensions, extName)
}
}
return extensions
}
func addPathExtensions(doc2 *openapi2.T, path string, extensions map[string]interface{}) {
if doc2.Paths == nil {
doc2.Paths = make(map[string]*openapi2.PathItem)
}
pathItem := doc2.Paths[path]
if pathItem == nil {
pathItem = &openapi2.PathItem{}
doc2.Paths[path] = pathItem
}
pathItem.Extensions = extensions
}
kin-openapi-0.124.0/openapi2conv/openapi2_conv_test.go 0000664 0000000 0000000 00000040303 14604223742 0022627 0 ustar 00root root 0000000 0000000 package openapi2conv
import (
"context"
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi2"
"github.com/getkin/kin-openapi/openapi3"
)
func TestConvOpenAPIV3ToV2(t *testing.T) {
var doc3 openapi3.T
err := json.Unmarshal([]byte(exampleV3), &doc3)
require.NoError(t, err)
{
// Refs need resolving before we can Validate
sl := openapi3.NewLoader()
err = sl.ResolveRefsIn(&doc3, nil)
require.NoError(t, err)
err = doc3.Validate(context.Background())
require.NoError(t, err)
}
doc2, err := FromV3(&doc3)
require.NoError(t, err)
data, err := json.Marshal(doc2)
require.NoError(t, err)
require.JSONEq(t, exampleV2, string(data))
}
func TestConvOpenAPIV3ToV2WithReqBody(t *testing.T) {
var doc3 openapi3.T
err := json.Unmarshal([]byte(exampleRequestBodyV3), &doc3)
require.NoError(t, err)
{
// Refs need resolving before we can Validate
sl := openapi3.NewLoader()
err = sl.ResolveRefsIn(&doc3, nil)
require.NoError(t, err)
err = doc3.Validate(context.Background())
require.NoError(t, err)
}
doc2, err := FromV3(&doc3)
require.NoError(t, err)
data, err := json.Marshal(doc2)
require.NoError(t, err)
require.JSONEq(t, exampleRequestBodyV2, string(data))
}
func TestConvOpenAPIV2ToV3(t *testing.T) {
var doc2 openapi2.T
err := json.Unmarshal([]byte(exampleV2), &doc2)
require.NoError(t, err)
doc3, err := ToV3(&doc2)
require.NoError(t, err)
err = doc3.Validate(context.Background())
require.NoError(t, err)
data, err := json.Marshal(doc3)
require.NoError(t, err)
require.JSONEq(t, exampleV3, string(data))
}
const exampleV2 = `
{
"basePath": "/v2",
"consumes": [
"application/json",
"application/xml"
],
"definitions": {
"Error": {
"description": "Error response.",
"properties": {
"message": {
"type": "string"
}
},
"required": [
"message"
],
"type": "object"
},
"Item": {
"additionalProperties": true,
"properties": {
"foo": {
"type": "string",
"x-nullable": true
},
"quux": {
"$ref": "#/definitions/ItemExtension"
}
},
"type": "object"
},
"ItemExtension": {
"description": "It could be anything.",
"type": "boolean"
},
"foo": {
"description": "foo description",
"enum": [
"bar",
"baz"
],
"type": "string"
}
},
"externalDocs": {
"description": "Example Documentation",
"url": "https://example/doc/"
},
"host": "test.example.com",
"info": {
"title": "MyAPI",
"version": "0.1",
"x-info": "info extension"
},
"parameters": {
"banana": {
"in": "path",
"name": "banana",
"required": true,
"type": "string"
},
"post_form_ref": {
"description": "param description",
"in": "formData",
"name": "fileUpload2",
"required": true,
"type": "file",
"x-formData-name": "fileUpload2",
"x-mimetype": "text/plain"
},
"put_body": {
"in": "body",
"name": "banana",
"required": true,
"schema": {
"type": "string"
},
"x-originalParamName": "banana"
}
},
"paths": {
"/another/{banana}/{id}": {
"parameters": [
{
"$ref": "#/parameters/banana"
},
{
"in": "path",
"name": "id",
"required": true,
"type": "integer"
}
]
},
"/example": {
"delete": {
"description": "example delete",
"operationId": "example-delete",
"parameters": [
{
"description": "Only return results that intersect the provided bounding box.",
"in": "query",
"items": {
"type": "number"
},
"maxItems": 4,
"minItems": 4,
"name": "bbox",
"type": "array"
},
{
"in": "query",
"name": "x",
"type": "string",
"x-parameter": "parameter extension 1"
},
{
"default": 250,
"description": "The y parameter",
"in": "query",
"maximum": 10000,
"minimum": 1,
"name": "y",
"type": "integer"
}
],
"responses": {
"200": {
"description": "ok",
"schema": {
"items": {
"$ref": "#/definitions/Item"
},
"type": "array"
},
"headers": {
"ETag": {
"description": "The ETag (or entity tag) HTTP response header is an identifier for a specific version of a resource.",
"type": "string",
"maxLength": 64
}
}
},
"404": {
"description": "404 response"
},
"default": {
"description": "default response",
"x-response": "response extension 1"
}
},
"security": [
{
"get_security_0": [
"scope0",
"scope1"
],
"get_security_1": []
}
],
"summary": "example get",
"tags": [
"Example"
]
},
"get": {
"description": "example get",
"responses": {
"403": {
"$ref": "#/responses/ForbiddenError"
},
"404": {
"description": "404 response"
},
"default": {
"description": "default response"
}
},
"x-operation": "operation extension 1"
},
"head": {
"description": "example head",
"responses": {
"default": {
"description": "default response"
}
}
},
"options": {
"description": "example options",
"responses": {
"default": {
"description": "default response"
}
}
},
"patch": {
"consumes": [
"application/json",
"application/xml"
],
"description": "example patch",
"parameters": [
{
"in": "body",
"name": "patch_body",
"schema": {
"allOf": [
{
"$ref": "#/definitions/Item"
}
]
},
"x-originalParamName": "patch_body",
"x-requestBody": "requestbody extension 1"
}
],
"responses": {
"default": {
"description": "default response"
}
}
},
"post": {
"consumes": [
"multipart/form-data"
],
"description": "example post",
"parameters": [
{
"$ref": "#/parameters/post_form_ref"
},
{
"description": "param description",
"in": "formData",
"name": "fileUpload",
"type": "file",
"x-formData-name": "fileUpload",
"x-mimetype": "text/plain"
},
{
"description": "File Id",
"in": "query",
"name": "id",
"type": "integer"
},
{
"description": "Description of file contents",
"in": "formData",
"name": "note",
"type": "integer",
"x-formData-name": "note"
}
],
"responses": {
"default": {
"description": "default response"
}
}
},
"put": {
"description": "example put",
"parameters": [
{
"$ref": "#/parameters/put_body"
}
],
"responses": {
"default": {
"description": "default response"
}
}
},
"x-path": "path extension 1",
"x-path2": "path extension 2"
},
"/foo": {
"get": {
"operationId": "getFoo",
"consumes": [
"application/json",
"application/xml"
],
"parameters": [
{
"x-originalParamName": "foo",
"in": "body",
"name": "foo",
"schema": {
"$ref": "#/definitions/foo"
}
}
],
"responses": {
"default": {
"description": "OK",
"schema": {
"$ref": "#/definitions/foo"
}
}
},
"summary": "get foo"
}
}
},
"responses": {
"ForbiddenError": {
"description": "Insufficient permission to perform the requested action.",
"schema": {
"$ref": "#/definitions/Error"
}
}
},
"schemes": [
"https"
],
"security": [
{
"default_security_0": [
"scope0",
"scope1"
],
"default_security_1": []
}
],
"swagger": "2.0",
"tags": [
{
"description": "An example tag.",
"name": "Example"
}
],
"x-root": "root extension 1",
"x-root2": "root extension 2"
}
`
const exampleV3 = `
{
"components": {
"parameters": {
"banana": {
"in": "path",
"name": "banana",
"required": true,
"schema": {
"type": "string"
}
}
},
"requestBodies": {
"put_body": {
"content": {
"application/json": {
"schema": {
"type": "string"
}
},
"application/xml": {
"schema": {
"type": "string"
}
}
},
"required": true,
"x-originalParamName": "banana"
}
},
"responses": {
"ForbiddenError": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
},
"description": "Insufficient permission to perform the requested action."
}
},
"schemas": {
"Error": {
"description": "Error response.",
"properties": {
"message": {
"type": "string"
}
},
"required": [
"message"
],
"type": "object"
},
"Item": {
"additionalProperties": true,
"properties": {
"foo": {
"type": "string",
"nullable": true
},
"quux": {
"$ref": "#/components/schemas/ItemExtension"
}
},
"type": "object"
},
"ItemExtension": {
"description": "It could be anything.",
"type": "boolean"
},
"post_form_ref": {
"description": "param description",
"format": "binary",
"required": [
"fileUpload2"
],
"type": "string",
"x-formData-name": "fileUpload2",
"x-mimetype": "text/plain"
},
"foo": {
"description": "foo description",
"enum": [
"bar",
"baz"
],
"type": "string"
}
}
},
"externalDocs": {
"description": "Example Documentation",
"url": "https://example/doc/"
},
"info": {
"title": "MyAPI",
"version": "0.1",
"x-info": "info extension"
},
"openapi": "3.0.3",
"paths": {
"/another/{banana}/{id}": {
"parameters": [
{
"$ref": "#/components/parameters/banana"
},
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
]
},
"/example": {
"delete": {
"description": "example delete",
"operationId": "example-delete",
"parameters": [
{
"description": "Only return results that intersect the provided bounding box.",
"in": "query",
"name": "bbox",
"schema": {
"items": {
"type": "number"
},
"maxItems": 4,
"minItems": 4,
"type": "array"
}
},
{
"in": "query",
"name": "x",
"schema": {
"type": "string"
},
"x-parameter": "parameter extension 1"
},
{
"description": "The y parameter",
"in": "query",
"name": "y",
"schema": {
"default": 250,
"maximum": 10000,
"minimum": 1,
"type": "integer"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/Item"
},
"type": "array"
}
}
},
"description": "ok",
"headers": {
"ETag": {
"description": "The ETag (or entity tag) HTTP response header is an identifier for a specific version of a resource.",
"schema": {
"type": "string",
"maxLength": 64
}
}
}
},
"404": {
"description": "404 response"
},
"default": {
"description": "default response",
"x-response": "response extension 1"
}
},
"security": [
{
"get_security_0": [
"scope0",
"scope1"
],
"get_security_1": []
}
],
"summary": "example get",
"tags": [
"Example"
]
},
"get": {
"description": "example get",
"responses": {
"403": {
"$ref": "#/components/responses/ForbiddenError"
},
"404": {
"description": "404 response"
},
"default": {
"description": "default response"
}
},
"x-operation": "operation extension 1"
},
"head": {
"description": "example head",
"responses": {
"default": {
"description": "default response"
}
}
},
"options": {
"description": "example options",
"responses": {
"default": {
"description": "default response"
}
}
},
"patch": {
"description": "example patch",
"requestBody": {
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/Item"
}
]
}
},
"application/xml": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/Item"
}
]
}
}
},
"x-originalParamName": "patch_body",
"x-requestBody": "requestbody extension 1"
},
"responses": {
"default": {
"description": "default response"
}
}
},
"post": {
"description": "example post",
"parameters": [
{
"description": "File Id",
"in": "query",
"name": "id",
"schema": {
"type": "integer"
}
}
],
"requestBody": {
"content": {
"multipart/form-data": {
"schema": {
"properties": {
"fileUpload": {
"description": "param description",
"format": "binary",
"type": "string",
"x-formData-name": "fileUpload",
"x-mimetype": "text/plain"
},
"fileUpload2": {
"$ref": "#/components/schemas/post_form_ref"
},
"note": {
"description": "Description of file contents",
"type": "integer",
"x-formData-name": "note"
}
},
"required": [
"fileUpload2"
],
"type": "object"
}
}
}
},
"responses": {
"default": {
"description": "default response"
}
}
},
"put": {
"description": "example put",
"requestBody": {
"$ref": "#/components/requestBodies/put_body"
},
"responses": {
"default": {
"description": "default response"
}
}
},
"x-path": "path extension 1",
"x-path2": "path extension 2"
},
"/foo": {
"get": {
"operationId": "getFoo",
"requestBody": {
"x-originalParamName": "foo",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/foo"
}
},
"application/xml": {
"schema": {
"$ref": "#/components/schemas/foo"
}
}
}
},
"responses": {
"default": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/foo"
}
}
},
"description": "OK"
}
},
"summary": "get foo"
}
}
},
"security": [
{
"default_security_0": [
"scope0",
"scope1"
],
"default_security_1": []
}
],
"servers": [
{
"url": "https://test.example.com/v2"
}
],
"tags": [
{
"description": "An example tag.",
"name": "Example"
}
],
"x-root": "root extension 1",
"x-root2": "root extension 2"
}
`
const exampleRequestBodyV3 = `{
"info": {
"description": "Test Spec",
"title": "Test Spec",
"version": "0.0.0"
},
"components": {
"requestBodies": {
"FooBody": {
"content": {
"application/json": {
"schema": {
"properties": { "message": { "type": "string" } },
"type": "object"
}
}
},
"description": "test spec request body.",
"required": true
}
}
},
"paths": {
"/foo-path": {
"post": {
"requestBody": { "$ref": "#/components/requestBodies/FooBody" },
"responses": { "202": { "description": "Test spec post." } },
"summary": "Test spec path"
}
}
},
"servers": [{ "url": "http://localhost/" }],
"openapi": "3.0.3"
}
`
const exampleRequestBodyV2 = `{
"basePath": "/",
"consumes": ["application/json"],
"host": "localhost",
"info": {
"description": "Test Spec",
"title": "Test Spec",
"version": "0.0.0"
},
"parameters": {
"FooBody": {
"description": "test spec request body.",
"in": "body",
"name": "FooBody",
"required": true,
"schema": {
"properties": { "message": { "type": "string" } },
"type": "object"
}
}
},
"paths": {
"/foo-path": {
"post": {
"parameters": [{ "$ref": "#/parameters/FooBody" }],
"responses": { "202": { "description": "Test spec post." } },
"summary": "Test spec path"
}
}
},
"schemes": ["http"],
"swagger": "2.0"
}
`
kin-openapi-0.124.0/openapi2conv/testdata/ 0000775 0000000 0000000 00000000000 14604223742 0020310 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi2conv/testdata/swagger.json 0000777 0000000 0000000 00000000000 14604223742 0031143 2../../openapi2/testdata/swagger.json ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/ 0000775 0000000 0000000 00000000000 14604223742 0015612 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/additionalProperties_test.go 0000664 0000000 0000000 00000001505 14604223742 0023366 0 ustar 00root root 0000000 0000000 package openapi3_test
import (
"bytes"
"context"
"os"
"testing"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
"github.com/getkin/kin-openapi/openapi3"
)
func TestMarshalAdditionalProperties(t *testing.T) {
ctx := context.Background()
data, err := os.ReadFile("testdata/test.openapi.additionalproperties.yml")
require.NoError(t, err)
loader := openapi3.NewLoader()
loader.IsExternalRefsAllowed = true
spec, err := loader.LoadFromData(data)
require.NoError(t, err)
err = spec.Validate(ctx)
require.NoError(t, err)
var buf bytes.Buffer
enc := yaml.NewEncoder(&buf)
enc.SetIndent(2)
err = enc.Encode(spec)
require.NoError(t, err)
// Load the doc from the serialized yaml.
spec2, err := loader.LoadFromData(buf.Bytes())
require.NoError(t, err)
err = spec2.Validate(ctx)
require.NoError(t, err)
}
kin-openapi-0.124.0/openapi3/callback.go 0000664 0000000 0000000 00000002607 14604223742 0017702 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"sort"
)
// Callback is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#callback-object
type Callback struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
m map[string]*PathItem
}
// NewCallback builds a Callback object with path items in insertion order.
func NewCallback(opts ...NewCallbackOption) *Callback {
Callback := NewCallbackWithCapacity(len(opts))
for _, opt := range opts {
opt(Callback)
}
return Callback
}
// NewCallbackOption describes options to NewCallback func
type NewCallbackOption func(*Callback)
// WithCallback adds Callback as an option to NewCallback
func WithCallback(cb string, pathItem *PathItem) NewCallbackOption {
return func(callback *Callback) {
if p := pathItem; p != nil && cb != "" {
callback.Set(cb, p)
}
}
}
// Validate returns an error if Callback does not comply with the OpenAPI spec.
func (callback *Callback) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
keys := make([]string, 0, callback.Len())
for key := range callback.Map() {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
v := callback.Value(key)
if err := v.Validate(ctx); err != nil {
return err
}
}
return validateExtensions(ctx, callback.Extensions)
}
kin-openapi-0.124.0/openapi3/components.go 0000664 0000000 0000000 00000025436 14604223742 0020340 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"encoding/json"
"fmt"
"sort"
"github.com/go-openapi/jsonpointer"
)
type (
Callbacks map[string]*CallbackRef
Examples map[string]*ExampleRef
Headers map[string]*HeaderRef
Links map[string]*LinkRef
ParametersMap map[string]*ParameterRef
RequestBodies map[string]*RequestBodyRef
ResponseBodies map[string]*ResponseRef
Schemas map[string]*SchemaRef
SecuritySchemes map[string]*SecuritySchemeRef
)
// Components is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#components-object
type Components struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Schemas Schemas `json:"schemas,omitempty" yaml:"schemas,omitempty"`
Parameters ParametersMap `json:"parameters,omitempty" yaml:"parameters,omitempty"`
Headers Headers `json:"headers,omitempty" yaml:"headers,omitempty"`
RequestBodies RequestBodies `json:"requestBodies,omitempty" yaml:"requestBodies,omitempty"`
Responses ResponseBodies `json:"responses,omitempty" yaml:"responses,omitempty"`
SecuritySchemes SecuritySchemes `json:"securitySchemes,omitempty" yaml:"securitySchemes,omitempty"`
Examples Examples `json:"examples,omitempty" yaml:"examples,omitempty"`
Links Links `json:"links,omitempty" yaml:"links,omitempty"`
Callbacks Callbacks `json:"callbacks,omitempty" yaml:"callbacks,omitempty"`
}
func NewComponents() Components {
return Components{}
}
// MarshalJSON returns the JSON encoding of Components.
func (components Components) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{}, 9+len(components.Extensions))
for k, v := range components.Extensions {
m[k] = v
}
if x := components.Schemas; len(x) != 0 {
m["schemas"] = x
}
if x := components.Parameters; len(x) != 0 {
m["parameters"] = x
}
if x := components.Headers; len(x) != 0 {
m["headers"] = x
}
if x := components.RequestBodies; len(x) != 0 {
m["requestBodies"] = x
}
if x := components.Responses; len(x) != 0 {
m["responses"] = x
}
if x := components.SecuritySchemes; len(x) != 0 {
m["securitySchemes"] = x
}
if x := components.Examples; len(x) != 0 {
m["examples"] = x
}
if x := components.Links; len(x) != 0 {
m["links"] = x
}
if x := components.Callbacks; len(x) != 0 {
m["callbacks"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets Components to a copy of data.
func (components *Components) UnmarshalJSON(data []byte) error {
type ComponentsBis Components
var x ComponentsBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "schemas")
delete(x.Extensions, "parameters")
delete(x.Extensions, "headers")
delete(x.Extensions, "requestBodies")
delete(x.Extensions, "responses")
delete(x.Extensions, "securitySchemes")
delete(x.Extensions, "examples")
delete(x.Extensions, "links")
delete(x.Extensions, "callbacks")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*components = Components(x)
return nil
}
// Validate returns an error if Components does not comply with the OpenAPI spec.
func (components *Components) Validate(ctx context.Context, opts ...ValidationOption) (err error) {
ctx = WithValidationOptions(ctx, opts...)
schemas := make([]string, 0, len(components.Schemas))
for name := range components.Schemas {
schemas = append(schemas, name)
}
sort.Strings(schemas)
for _, k := range schemas {
v := components.Schemas[k]
if err = ValidateIdentifier(k); err != nil {
return fmt.Errorf("schema %q: %w", k, err)
}
if err = v.Validate(ctx); err != nil {
return fmt.Errorf("schema %q: %w", k, err)
}
}
parameters := make([]string, 0, len(components.Parameters))
for name := range components.Parameters {
parameters = append(parameters, name)
}
sort.Strings(parameters)
for _, k := range parameters {
v := components.Parameters[k]
if err = ValidateIdentifier(k); err != nil {
return fmt.Errorf("parameter %q: %w", k, err)
}
if err = v.Validate(ctx); err != nil {
return fmt.Errorf("parameter %q: %w", k, err)
}
}
requestBodies := make([]string, 0, len(components.RequestBodies))
for name := range components.RequestBodies {
requestBodies = append(requestBodies, name)
}
sort.Strings(requestBodies)
for _, k := range requestBodies {
v := components.RequestBodies[k]
if err = ValidateIdentifier(k); err != nil {
return fmt.Errorf("request body %q: %w", k, err)
}
if err = v.Validate(ctx); err != nil {
return fmt.Errorf("request body %q: %w", k, err)
}
}
responses := make([]string, 0, len(components.Responses))
for name := range components.Responses {
responses = append(responses, name)
}
sort.Strings(responses)
for _, k := range responses {
if err = ValidateIdentifier(k); err != nil {
return fmt.Errorf("response %q: %w", k, err)
}
v := components.Responses[k]
if err = v.Validate(ctx); err != nil {
return fmt.Errorf("response %q: %w", k, err)
}
}
headers := make([]string, 0, len(components.Headers))
for name := range components.Headers {
headers = append(headers, name)
}
sort.Strings(headers)
for _, k := range headers {
v := components.Headers[k]
if err = ValidateIdentifier(k); err != nil {
return fmt.Errorf("header %q: %w", k, err)
}
if err = v.Validate(ctx); err != nil {
return fmt.Errorf("header %q: %w", k, err)
}
}
securitySchemes := make([]string, 0, len(components.SecuritySchemes))
for name := range components.SecuritySchemes {
securitySchemes = append(securitySchemes, name)
}
sort.Strings(securitySchemes)
for _, k := range securitySchemes {
v := components.SecuritySchemes[k]
if err = ValidateIdentifier(k); err != nil {
return fmt.Errorf("security scheme %q: %w", k, err)
}
if err = v.Validate(ctx); err != nil {
return fmt.Errorf("security scheme %q: %w", k, err)
}
}
examples := make([]string, 0, len(components.Examples))
for name := range components.Examples {
examples = append(examples, name)
}
sort.Strings(examples)
for _, k := range examples {
v := components.Examples[k]
if err = ValidateIdentifier(k); err != nil {
return fmt.Errorf("example %q: %w", k, err)
}
if err = v.Validate(ctx); err != nil {
return fmt.Errorf("example %q: %w", k, err)
}
}
links := make([]string, 0, len(components.Links))
for name := range components.Links {
links = append(links, name)
}
sort.Strings(links)
for _, k := range links {
v := components.Links[k]
if err = ValidateIdentifier(k); err != nil {
return fmt.Errorf("link %q: %w", k, err)
}
if err = v.Validate(ctx); err != nil {
return fmt.Errorf("link %q: %w", k, err)
}
}
callbacks := make([]string, 0, len(components.Callbacks))
for name := range components.Callbacks {
callbacks = append(callbacks, name)
}
sort.Strings(callbacks)
for _, k := range callbacks {
v := components.Callbacks[k]
if err = ValidateIdentifier(k); err != nil {
return fmt.Errorf("callback %q: %w", k, err)
}
if err = v.Validate(ctx); err != nil {
return fmt.Errorf("callback %q: %w", k, err)
}
}
return validateExtensions(ctx, components.Extensions)
}
var _ jsonpointer.JSONPointable = (*Schemas)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (m Schemas) JSONLookup(token string) (interface{}, error) {
if v, ok := m[token]; !ok || v == nil {
return nil, fmt.Errorf("no schema %q", token)
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
return v.Value, nil
}
}
var _ jsonpointer.JSONPointable = (*ParametersMap)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (m ParametersMap) JSONLookup(token string) (interface{}, error) {
if v, ok := m[token]; !ok || v == nil {
return nil, fmt.Errorf("no parameter %q", token)
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
return v.Value, nil
}
}
var _ jsonpointer.JSONPointable = (*Headers)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (m Headers) JSONLookup(token string) (interface{}, error) {
if v, ok := m[token]; !ok || v == nil {
return nil, fmt.Errorf("no header %q", token)
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
return v.Value, nil
}
}
var _ jsonpointer.JSONPointable = (*RequestBodyRef)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (m RequestBodies) JSONLookup(token string) (interface{}, error) {
if v, ok := m[token]; !ok || v == nil {
return nil, fmt.Errorf("no request body %q", token)
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
return v.Value, nil
}
}
var _ jsonpointer.JSONPointable = (*ResponseRef)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (m ResponseBodies) JSONLookup(token string) (interface{}, error) {
if v, ok := m[token]; !ok || v == nil {
return nil, fmt.Errorf("no response body %q", token)
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
return v.Value, nil
}
}
var _ jsonpointer.JSONPointable = (*SecuritySchemes)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (m SecuritySchemes) JSONLookup(token string) (interface{}, error) {
if v, ok := m[token]; !ok || v == nil {
return nil, fmt.Errorf("no security scheme body %q", token)
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
return v.Value, nil
}
}
var _ jsonpointer.JSONPointable = (*Examples)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (m Examples) JSONLookup(token string) (interface{}, error) {
if v, ok := m[token]; !ok || v == nil {
return nil, fmt.Errorf("no example body %q", token)
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
return v.Value, nil
}
}
var _ jsonpointer.JSONPointable = (*Links)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (m Links) JSONLookup(token string) (interface{}, error) {
if v, ok := m[token]; !ok || v == nil {
return nil, fmt.Errorf("no link body %q", token)
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
return v.Value, nil
}
}
var _ jsonpointer.JSONPointable = (*Callbacks)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (m Callbacks) JSONLookup(token string) (interface{}, error) {
if v, ok := m[token]; !ok || v == nil {
return nil, fmt.Errorf("no callback body %q", token)
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
return v.Value, nil
}
}
kin-openapi-0.124.0/openapi3/contact.go 0000664 0000000 0000000 00000003104 14604223742 0017572 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"encoding/json"
)
// Contact is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#contact-object
type Contact struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
URL string `json:"url,omitempty" yaml:"url,omitempty"`
Email string `json:"email,omitempty" yaml:"email,omitempty"`
}
// MarshalJSON returns the JSON encoding of Contact.
func (contact Contact) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{}, 3+len(contact.Extensions))
for k, v := range contact.Extensions {
m[k] = v
}
if x := contact.Name; x != "" {
m["name"] = x
}
if x := contact.URL; x != "" {
m["url"] = x
}
if x := contact.Email; x != "" {
m["email"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets Contact to a copy of data.
func (contact *Contact) UnmarshalJSON(data []byte) error {
type ContactBis Contact
var x ContactBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "name")
delete(x.Extensions, "url")
delete(x.Extensions, "email")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*contact = Contact(x)
return nil
}
// Validate returns an error if Contact does not comply with the OpenAPI spec.
func (contact *Contact) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
return validateExtensions(ctx, contact.Extensions)
}
kin-openapi-0.124.0/openapi3/content.go 0000664 0000000 0000000 00000006201 14604223742 0017612 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"sort"
"strings"
)
// Content is specified by OpenAPI/Swagger 3.0 standard.
type Content map[string]*MediaType
func NewContent() Content {
return make(map[string]*MediaType)
}
func NewContentWithSchema(schema *Schema, consumes []string) Content {
if len(consumes) == 0 {
return Content{
"*/*": NewMediaType().WithSchema(schema),
}
}
content := make(map[string]*MediaType, len(consumes))
for _, mediaType := range consumes {
content[mediaType] = NewMediaType().WithSchema(schema)
}
return content
}
func NewContentWithSchemaRef(schema *SchemaRef, consumes []string) Content {
if len(consumes) == 0 {
return Content{
"*/*": NewMediaType().WithSchemaRef(schema),
}
}
content := make(map[string]*MediaType, len(consumes))
for _, mediaType := range consumes {
content[mediaType] = NewMediaType().WithSchemaRef(schema)
}
return content
}
func NewContentWithJSONSchema(schema *Schema) Content {
return Content{
"application/json": NewMediaType().WithSchema(schema),
}
}
func NewContentWithJSONSchemaRef(schema *SchemaRef) Content {
return Content{
"application/json": NewMediaType().WithSchemaRef(schema),
}
}
func NewContentWithFormDataSchema(schema *Schema) Content {
return Content{
"multipart/form-data": NewMediaType().WithSchema(schema),
}
}
func NewContentWithFormDataSchemaRef(schema *SchemaRef) Content {
return Content{
"multipart/form-data": NewMediaType().WithSchemaRef(schema),
}
}
func (content Content) Get(mime string) *MediaType {
// If the mime is empty then short-circuit to the wildcard.
// We do this here so that we catch only the specific case of
// and empty mime rather than a present, but invalid, mime type.
if mime == "" {
return content["*/*"]
}
// Start by making the most specific match possible
// by using the mime type in full.
if v := content[mime]; v != nil {
return v
}
// If an exact match is not found then we strip all
// metadata from the mime type and only use the x/y
// portion.
i := strings.IndexByte(mime, ';')
if i < 0 {
// If there is no metadata then preserve the full mime type
// string for later wildcard searches.
i = len(mime)
}
mime = mime[:i]
if v := content[mime]; v != nil {
return v
}
// If the x/y pattern has no specific match then we
// try the x/* pattern.
i = strings.IndexByte(mime, '/')
if i < 0 {
// In the case that the given mime type is not valid because it is
// missing the subtype we return nil so that this does not accidentally
// resolve with the wildcard.
return nil
}
mime = mime[:i] + "/*"
if v := content[mime]; v != nil {
return v
}
// Finally, the most generic match of */* is returned
// as a catch-all.
return content["*/*"]
}
// Validate returns an error if Content does not comply with the OpenAPI spec.
func (content Content) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
keys := make([]string, 0, len(content))
for key := range content {
keys = append(keys, key)
}
sort.Strings(keys)
for _, k := range keys {
v := content[k]
if err := v.Validate(ctx); err != nil {
return err
}
}
return nil
}
kin-openapi-0.124.0/openapi3/content_test.go 0000664 0000000 0000000 00000004354 14604223742 0020660 0 ustar 00root root 0000000 0000000 package openapi3
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestContent_Get(t *testing.T) {
fallback := NewMediaType()
wildcard := NewMediaType()
stripped := NewMediaType()
fullMatch := NewMediaType()
content := Content{
"*/*": fallback,
"application/*": wildcard,
"application/json": stripped,
"application/json;encoding=utf-8": fullMatch,
}
contentWithoutWildcards := Content{
"application/json": stripped,
"application/json;encoding=utf-8": fullMatch,
}
tests := []struct {
name string
content Content
mime string
want *MediaType
}{
{
name: "missing",
content: contentWithoutWildcards,
mime: "text/plain;encoding=utf-8",
want: nil,
},
{
name: "full match",
content: content,
mime: "application/json;encoding=utf-8",
want: fullMatch,
},
{
name: "stripped match",
content: content,
mime: "application/json;encoding=utf-16",
want: stripped,
},
{
name: "wildcard match",
content: content,
mime: "application/yaml;encoding=utf-16",
want: wildcard,
},
{
name: "fallback match",
content: content,
mime: "text/plain;encoding=utf-16",
want: fallback,
},
{
name: "invalid mime type",
content: content,
mime: "text;encoding=utf16",
want: nil,
},
{
name: "missing no encoding",
content: contentWithoutWildcards,
mime: "text/plain",
want: nil,
},
{
name: "stripped match no encoding",
content: content,
mime: "application/json",
want: stripped,
},
{
name: "wildcard match no encoding",
content: content,
mime: "application/yaml",
want: wildcard,
},
{
name: "fallback match no encoding",
content: content,
mime: "text/plain",
want: fallback,
},
{
name: "invalid mime type no encoding",
content: content,
mime: "text",
want: nil,
},
{
name: "missing mime type",
content: content,
mime: "",
want: fallback,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.content.Get(tt.mime)
require.Same(t, tt.want, got)
})
}
}
kin-openapi-0.124.0/openapi3/discriminator.go 0000664 0000000 0000000 00000003167 14604223742 0021017 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"encoding/json"
)
// Discriminator is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#discriminator-object
type Discriminator struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
PropertyName string `json:"propertyName" yaml:"propertyName"` // required
Mapping map[string]string `json:"mapping,omitempty" yaml:"mapping,omitempty"`
}
// MarshalJSON returns the JSON encoding of Discriminator.
func (discriminator Discriminator) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{}, 2+len(discriminator.Extensions))
for k, v := range discriminator.Extensions {
m[k] = v
}
m["propertyName"] = discriminator.PropertyName
if x := discriminator.Mapping; len(x) != 0 {
m["mapping"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets Discriminator to a copy of data.
func (discriminator *Discriminator) UnmarshalJSON(data []byte) error {
type DiscriminatorBis Discriminator
var x DiscriminatorBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "propertyName")
delete(x.Extensions, "mapping")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*discriminator = Discriminator(x)
return nil
}
// Validate returns an error if Discriminator does not comply with the OpenAPI spec.
func (discriminator *Discriminator) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
return validateExtensions(ctx, discriminator.Extensions)
}
kin-openapi-0.124.0/openapi3/discriminator_test.go 0000664 0000000 0000000 00000001730 14604223742 0022050 0 ustar 00root root 0000000 0000000 package openapi3
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestParsingDiscriminator(t *testing.T) {
const spec = `
{
"openapi": "3.0.0",
"info": {
"version": "1.0.0",
"title": "title",
"description": "desc",
"contact": {
"email": "email"
}
},
"paths": {},
"components": {
"schemas": {
"MyResponseType": {
"discriminator": {
"mapping": {
"cat": "#/components/schemas/Cat",
"dog": "#/components/schemas/Dog"
},
"propertyName": "pet_type"
},
"oneOf": [
{
"$ref": "#/components/schemas/Cat"
},
{
"$ref": "#/components/schemas/Dog"
}
]
},
"Cat": {"enum": ["chat"]},
"Dog": {"enum": ["chien"]}
}
}
}
`
loader := NewLoader()
doc, err := loader.LoadFromData([]byte(spec))
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
require.Len(t, doc.Components.Schemas["MyResponseType"].Value.Discriminator.Mapping, 2)
}
kin-openapi-0.124.0/openapi3/doc.go 0000664 0000000 0000000 00000000257 14604223742 0016712 0 ustar 00root root 0000000 0000000 // Package openapi3 parses and writes OpenAPI 3 specification documents.
//
// See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md
package openapi3
kin-openapi-0.124.0/openapi3/encoding.go 0000664 0000000 0000000 00000007634 14604223742 0017741 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"encoding/json"
"fmt"
"sort"
)
// Encoding is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#encoding-object
type Encoding struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
ContentType string `json:"contentType,omitempty" yaml:"contentType,omitempty"`
Headers Headers `json:"headers,omitempty" yaml:"headers,omitempty"`
Style string `json:"style,omitempty" yaml:"style,omitempty"`
Explode *bool `json:"explode,omitempty" yaml:"explode,omitempty"`
AllowReserved bool `json:"allowReserved,omitempty" yaml:"allowReserved,omitempty"`
}
func NewEncoding() *Encoding {
return &Encoding{}
}
func (encoding *Encoding) WithHeader(name string, header *Header) *Encoding {
return encoding.WithHeaderRef(name, &HeaderRef{
Value: header,
})
}
func (encoding *Encoding) WithHeaderRef(name string, ref *HeaderRef) *Encoding {
headers := encoding.Headers
if headers == nil {
headers = make(map[string]*HeaderRef)
encoding.Headers = headers
}
headers[name] = ref
return encoding
}
// MarshalJSON returns the JSON encoding of Encoding.
func (encoding Encoding) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{}, 5+len(encoding.Extensions))
for k, v := range encoding.Extensions {
m[k] = v
}
if x := encoding.ContentType; x != "" {
m["contentType"] = x
}
if x := encoding.Headers; len(x) != 0 {
m["headers"] = x
}
if x := encoding.Style; x != "" {
m["style"] = x
}
if x := encoding.Explode; x != nil {
m["explode"] = x
}
if x := encoding.AllowReserved; x {
m["allowReserved"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets Encoding to a copy of data.
func (encoding *Encoding) UnmarshalJSON(data []byte) error {
type EncodingBis Encoding
var x EncodingBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "contentType")
delete(x.Extensions, "headers")
delete(x.Extensions, "style")
delete(x.Extensions, "explode")
delete(x.Extensions, "allowReserved")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*encoding = Encoding(x)
return nil
}
// SerializationMethod returns a serialization method of request body.
// When serialization method is not defined the method returns the default serialization method.
func (encoding *Encoding) SerializationMethod() *SerializationMethod {
sm := &SerializationMethod{Style: SerializationForm, Explode: true}
if encoding != nil {
if encoding.Style != "" {
sm.Style = encoding.Style
}
if encoding.Explode != nil {
sm.Explode = *encoding.Explode
}
}
return sm
}
// Validate returns an error if Encoding does not comply with the OpenAPI spec.
func (encoding *Encoding) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if encoding == nil {
return nil
}
headers := make([]string, 0, len(encoding.Headers))
for k := range encoding.Headers {
headers = append(headers, k)
}
sort.Strings(headers)
for _, k := range headers {
v := encoding.Headers[k]
if err := ValidateIdentifier(k); err != nil {
return nil
}
if err := v.Validate(ctx); err != nil {
return nil
}
}
// Validate a media types's serialization method.
sm := encoding.SerializationMethod()
switch {
case sm.Style == SerializationForm && sm.Explode,
sm.Style == SerializationForm && !sm.Explode,
sm.Style == SerializationSpaceDelimited && sm.Explode,
sm.Style == SerializationSpaceDelimited && !sm.Explode,
sm.Style == SerializationPipeDelimited && sm.Explode,
sm.Style == SerializationPipeDelimited && !sm.Explode,
sm.Style == SerializationDeepObject && sm.Explode:
default:
return fmt.Errorf("serialization method with style=%q and explode=%v is not supported by media type", sm.Style, sm.Explode)
}
return validateExtensions(ctx, encoding.Extensions)
}
kin-openapi-0.124.0/openapi3/encoding_test.go 0000664 0000000 0000000 00000004456 14604223742 0020777 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
)
func TestEncodingJSON(t *testing.T) {
t.Log("Marshal *openapi3.Encoding to JSON")
data, err := json.Marshal(encoding())
require.NoError(t, err)
require.NotEmpty(t, data)
t.Log("Unmarshal *openapi3.Encoding from JSON")
docA := &Encoding{}
err = json.Unmarshal(encodingJSON, &docA)
require.NoError(t, err)
require.NotEmpty(t, docA)
t.Log("Validate *openapi3.Encoding")
err = docA.Validate(context.Background())
require.NoError(t, err)
t.Log("Ensure representations match")
dataA, err := json.Marshal(docA)
require.NoError(t, err)
require.JSONEq(t, string(data), string(encodingJSON))
require.JSONEq(t, string(data), string(dataA))
}
var encodingJSON = []byte(`
{
"contentType": "application/json",
"headers": {
"someHeader": {}
},
"style": "form",
"explode": true,
"allowReserved": true
}
`)
func encoding() *Encoding {
return &Encoding{
ContentType: "application/json",
Headers: map[string]*HeaderRef{
"someHeader": {
Value: &Header{},
},
},
Style: "form",
Explode: BoolPtr(true),
AllowReserved: true,
}
}
func TestEncodingSerializationMethod(t *testing.T) {
testCases := []struct {
name string
enc *Encoding
want *SerializationMethod
}{
{
name: "default",
want: &SerializationMethod{Style: SerializationForm, Explode: true},
},
{
name: "encoding with style",
enc: &Encoding{Style: SerializationSpaceDelimited},
want: &SerializationMethod{Style: SerializationSpaceDelimited, Explode: true},
},
{
name: "encoding with explode",
enc: &Encoding{Explode: BoolPtr(true)},
want: &SerializationMethod{Style: SerializationForm, Explode: true},
},
{
name: "encoding with no explode",
enc: &Encoding{Explode: BoolPtr(false)},
want: &SerializationMethod{Style: SerializationForm, Explode: false},
},
{
name: "encoding with style and explode ",
enc: &Encoding{Style: SerializationSpaceDelimited, Explode: BoolPtr(false)},
want: &SerializationMethod{Style: SerializationSpaceDelimited, Explode: false},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
got := tc.enc.SerializationMethod()
require.EqualValues(t, got, tc.want, "got %#v, want %#v", got, tc.want)
})
}
}
kin-openapi-0.124.0/openapi3/errors.go 0000664 0000000 0000000 00000002373 14604223742 0017462 0 ustar 00root root 0000000 0000000 package openapi3
import (
"bytes"
"errors"
)
// MultiError is a collection of errors, intended for when
// multiple issues need to be reported upstream
type MultiError []error
func (me MultiError) Error() string {
return spliceErr(" | ", me)
}
func spliceErr(sep string, errs []error) string {
buff := &bytes.Buffer{}
for i, e := range errs {
buff.WriteString(e.Error())
if i != len(errs)-1 {
buff.WriteString(sep)
}
}
return buff.String()
}
// Is allows you to determine if a generic error is in fact a MultiError using `errors.Is()`
// It will also return true if any of the contained errors match target
func (me MultiError) Is(target error) bool {
if _, ok := target.(MultiError); ok {
return true
}
for _, e := range me {
if errors.Is(e, target) {
return true
}
}
return false
}
// As allows you to use `errors.As()` to set target to the first error within the multi error that matches the target type
func (me MultiError) As(target interface{}) bool {
for _, e := range me {
if errors.As(e, target) {
return true
}
}
return false
}
type multiErrorForOneOf MultiError
func (meo multiErrorForOneOf) Error() string {
return spliceErr(" Or ", meo)
}
func (meo multiErrorForOneOf) Unwrap() error {
return MultiError(meo)
}
kin-openapi-0.124.0/openapi3/example.go 0000664 0000000 0000000 00000004267 14604223742 0017605 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"encoding/json"
"errors"
)
// Example is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#example-object
type Example struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Summary string `json:"summary,omitempty" yaml:"summary,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Value interface{} `json:"value,omitempty" yaml:"value,omitempty"`
ExternalValue string `json:"externalValue,omitempty" yaml:"externalValue,omitempty"`
}
func NewExample(value interface{}) *Example {
return &Example{Value: value}
}
// MarshalJSON returns the JSON encoding of Example.
func (example Example) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{}, 4+len(example.Extensions))
for k, v := range example.Extensions {
m[k] = v
}
if x := example.Summary; x != "" {
m["summary"] = x
}
if x := example.Description; x != "" {
m["description"] = x
}
if x := example.Value; x != nil {
m["value"] = x
}
if x := example.ExternalValue; x != "" {
m["externalValue"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets Example to a copy of data.
func (example *Example) UnmarshalJSON(data []byte) error {
type ExampleBis Example
var x ExampleBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "summary")
delete(x.Extensions, "description")
delete(x.Extensions, "value")
delete(x.Extensions, "externalValue")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*example = Example(x)
return nil
}
// Validate returns an error if Example does not comply with the OpenAPI spec.
func (example *Example) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if example.Value != nil && example.ExternalValue != "" {
return errors.New("value and externalValue are mutually exclusive")
}
if example.Value == nil && example.ExternalValue == "" {
return errors.New("no value or externalValue field")
}
return validateExtensions(ctx, example.Extensions)
}
kin-openapi-0.124.0/openapi3/example_test.go 0000664 0000000 0000000 00000002103 14604223742 0020627 0 ustar 00root root 0000000 0000000 package openapi3
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
)
func TestExampleJSON(t *testing.T) {
t.Log("Marshal *openapi3.Example to JSON")
data, err := json.Marshal(example())
require.NoError(t, err)
require.NotEmpty(t, data)
t.Log("Unmarshal *openapi3.Example from JSON")
docA := &Example{}
err = json.Unmarshal(exampleJSON, &docA)
require.NoError(t, err)
require.NotEmpty(t, data)
t.Log("Ensure representations match")
dataA, err := json.Marshal(docA)
require.NoError(t, err)
require.JSONEq(t, string(data), string(exampleJSON))
require.JSONEq(t, string(data), string(dataA))
}
var exampleJSON = []byte(`
{
"summary": "An example of a cat",
"value": {
"name": "Fluffy",
"petType": "Cat",
"color": "White",
"gender": "male",
"breed": "Persian"
}
}
`)
func example() *Example {
value := map[string]string{
"name": "Fluffy",
"petType": "Cat",
"color": "White",
"gender": "male",
"breed": "Persian",
}
return &Example{
Summary: "An example of a cat",
Value: value,
}
}
kin-openapi-0.124.0/openapi3/example_validation.go 0000664 0000000 0000000 00000000673 14604223742 0022014 0 ustar 00root root 0000000 0000000 package openapi3
import "context"
func validateExampleValue(ctx context.Context, input interface{}, schema *Schema) error {
opts := make([]SchemaValidationOption, 0, 2)
if vo := getValidationOptions(ctx); vo.examplesValidationAsReq {
opts = append(opts, VisitAsRequest())
} else if vo.examplesValidationAsRes {
opts = append(opts, VisitAsResponse())
}
opts = append(opts, MultiErrors())
return schema.VisitJSON(input, opts...)
}
kin-openapi-0.124.0/openapi3/example_validation_test.go 0000664 0000000 0000000 00000031022 14604223742 0023043 0 ustar 00root root 0000000 0000000 package openapi3
import (
"bytes"
"testing"
"github.com/stretchr/testify/require"
)
func TestExamplesSchemaValidation(t *testing.T) {
type testCase struct {
name string
requestSchemaExample string
responseSchemaExample string
mediaTypeRequestExample string
mediaTypeResponseExample string
readWriteOnlyMediaTypeRequestExample string
readWriteOnlyMediaTypeResponseExample string
parametersExample string
componentExamples string
errContains string
}
testCases := []testCase{
{
name: "invalid_parameter_examples",
parametersExample: `
examples:
param1example:
value: abcd
`,
errContains: `invalid paths: invalid path /user: invalid operation POST: param1example`,
},
{
name: "valid_parameter_examples",
parametersExample: `
examples:
param1example:
value: 1
`,
},
{
name: "invalid_parameter_example",
parametersExample: `
example: abcd
`,
errContains: `invalid path /user: invalid operation POST: invalid example`,
},
{
name: "valid_parameter_example",
parametersExample: `
example: 1
`,
},
{
name: "invalid_component_examples",
mediaTypeRequestExample: `
examples:
BadUser:
$ref: '#/components/examples/BadUser'
`,
componentExamples: `
examples:
BadUser:
value:
username: "]bad["
email: bad
password: short
`,
errContains: `invalid paths: invalid path /user: invalid operation POST: example BadUser`,
},
{
name: "valid_component_examples",
mediaTypeRequestExample: `
examples:
BadUser:
$ref: '#/components/examples/BadUser'
`,
componentExamples: `
examples:
BadUser:
value:
username: good
email: good@mail.com
password: password
`,
},
{
name: "invalid_mediatype_examples",
mediaTypeRequestExample: `
example:
username: "]bad["
email: bad
password: short
`,
errContains: `invalid path /user: invalid operation POST: invalid example`,
},
{
name: "valid_mediatype_examples",
mediaTypeRequestExample: `
example:
username: good
email: good@mail.com
password: password
`,
},
{
name: "invalid_schema_request_example",
requestSchemaExample: `
example:
username: good
email: good@email.com
# missing password
`,
errContains: `schema "CreateUserRequest": invalid example`,
},
{
name: "valid_schema_request_example",
requestSchemaExample: `
example:
username: good
email: good@email.com
password: password
`,
},
{
name: "invalid_schema_response_example",
responseSchemaExample: `
example:
user_id: 1
# missing access_token
`,
errContains: `schema "CreateUserResponse": invalid example`,
},
{
name: "valid_schema_response_example",
responseSchemaExample: `
example:
user_id: 1
access_token: "abcd"
`,
},
{
name: "valid_readonly_writeonly_examples",
readWriteOnlyMediaTypeRequestExample: `
examples:
ReadWriteOnlyRequest:
$ref: '#/components/examples/ReadWriteOnlyRequestData'
`,
readWriteOnlyMediaTypeResponseExample: `
examples:
ReadWriteOnlyResponse:
$ref: '#/components/examples/ReadWriteOnlyResponseData'
`,
componentExamples: `
examples:
ReadWriteOnlyRequestData:
value:
username: user
password: password
ReadWriteOnlyResponseData:
value:
user_id: 4321
`,
},
{
name: "invalid_readonly_request_examples",
readWriteOnlyMediaTypeRequestExample: `
examples:
ReadWriteOnlyRequest:
$ref: '#/components/examples/ReadWriteOnlyRequestData'
`,
componentExamples: `
examples:
ReadWriteOnlyRequestData:
value:
username: user
password: password
user_id: 4321
`,
errContains: `ReadWriteOnlyRequest: readOnly property "user_id" in request`,
},
{
name: "invalid_writeonly_response_examples",
readWriteOnlyMediaTypeResponseExample: `
examples:
ReadWriteOnlyResponse:
$ref: '#/components/examples/ReadWriteOnlyResponseData'
`,
componentExamples: `
examples:
ReadWriteOnlyResponseData:
value:
password: password
user_id: 4321
`,
errContains: `ReadWriteOnlyResponse: writeOnly property "password" in response`,
},
}
testOptions := []struct {
name string
disableExamplesValidation bool
}{
{
name: "examples_validation_disabled",
disableExamplesValidation: true,
},
{
name: "examples_validation_enabled",
disableExamplesValidation: false,
},
}
t.Parallel()
for _, testOption := range testOptions {
testOption := testOption
t.Run(testOption.name, func(t *testing.T) {
t.Parallel()
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
spec := bytes.NewBufferString(`
openapi: 3.0.3
info:
title: An API
version: 1.2.3.4
paths:
/user:
post:
description: User creation.
operationId: createUser
parameters:
- name: param1
in: 'query'
schema:
format: int64
type: integer`)
spec.WriteString(tc.parametersExample)
spec.WriteString(`
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateUserRequest"
`)
spec.WriteString(tc.mediaTypeRequestExample)
spec.WriteString(`
description: Created user object
responses:
'204':
description: "success"
content:
application/json:
schema:
$ref: "#/components/schemas/CreateUserResponse"`)
spec.WriteString(tc.mediaTypeResponseExample)
spec.WriteString(`
/readWriteOnly:
post:
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ReadWriteOnlyData"
`)
spec.WriteString(tc.readWriteOnlyMediaTypeRequestExample)
spec.WriteString(`
responses:
'201':
description: a response
content:
application/json:
schema:
$ref: "#/components/schemas/ReadWriteOnlyData"`)
spec.WriteString(tc.readWriteOnlyMediaTypeResponseExample)
spec.WriteString(`
components:
schemas:
CreateUserRequest:`)
spec.WriteString(tc.requestSchemaExample)
spec.WriteString(`
required:
- username
- email
- password
properties:
username:
type: string
pattern: "^[ a-zA-Z0-9_-]+$"
minLength: 3
email:
type: string
pattern: "^[A-Za-z0-9+_.-]+@(.+)$"
password:
type: string
minLength: 7
type: object
CreateUserResponse:`)
spec.WriteString(tc.responseSchemaExample)
spec.WriteString(`
required:
- access_token
- user_id
properties:
access_token:
type: string
user_id:
format: int64
type: integer
type: object
ReadWriteOnlyData:
required:
# only required in request
- username
- password
# only required in response
- user_id
properties:
username:
type: string
default: default
writeOnly: true # only sent in a request
password:
type: string
default: default
writeOnly: true # only sent in a request
user_id:
format: int64
default: 1
type: integer
readOnly: true # only returned in a response
type: object
`)
spec.WriteString(tc.componentExamples)
loader := NewLoader()
doc, err := loader.LoadFromData(spec.Bytes())
require.NoError(t, err)
if testOption.disableExamplesValidation {
err = doc.Validate(loader.Context, DisableExamplesValidation())
} else {
err = doc.Validate(loader.Context, EnableExamplesValidation())
}
if tc.errContains != "" && !testOption.disableExamplesValidation {
require.Error(t, err)
require.ErrorContains(t, err, tc.errContains)
} else {
require.NoError(t, err)
}
})
}
})
}
}
func TestExampleObjectValidation(t *testing.T) {
type testCase struct {
name string
mediaTypeRequestExample string
componentExamples string
errContains string
}
testCases := []testCase{
{
name: "example_examples_mutually_exclusive",
mediaTypeRequestExample: `
examples:
BadUser:
$ref: '#/components/examples/BadUser'
example:
username: good
email: real@email.com
password: validpassword
`,
errContains: `invalid path /user: invalid operation POST: example and examples are mutually exclusive`,
componentExamples: `
examples:
BadUser:
value:
username: "]bad["
email: bad
password: short
`,
},
{
name: "example_without_value",
componentExamples: `
examples:
BadUser:
description: empty user example
`,
errContains: `invalid components: example "BadUser": no value or externalValue field`,
},
{
name: "value_externalValue_mutual_exclusion",
componentExamples: `
examples:
BadUser:
value:
username: good
email: real@email.com
password: validpassword
externalValue: 'http://example.com/examples/example'
`,
errContains: `invalid components: example "BadUser": value and externalValue are mutually exclusive`,
},
}
testOptions := []struct {
name string
disableExamplesValidation bool
}{
{
name: "examples_validation_disabled",
disableExamplesValidation: true,
},
{
name: "examples_validation_enabled",
disableExamplesValidation: false,
},
}
t.Parallel()
for _, testOption := range testOptions {
testOption := testOption
t.Run(testOption.name, func(t *testing.T) {
t.Parallel()
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
spec := bytes.NewBufferString(`
openapi: 3.0.3
info:
title: An API
version: 1.2.3.4
paths:
/user:
post:
description: User creation.
operationId: createUser
parameters:
- name: param1
in: 'query'
schema:
format: int64
type: integer
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/CreateUserRequest"
`)
spec.WriteString(tc.mediaTypeRequestExample)
spec.WriteString(`
description: Created user object
required: true
responses:
'204':
description: "success"
content:
application/json:
schema:
$ref: "#/components/schemas/CreateUserResponse"
components:
schemas:
CreateUserRequest:
required:
- username
- email
- password
properties:
username:
type: string
pattern: "^[ a-zA-Z0-9_-]+$"
minLength: 3
email:
type: string
pattern: "^[A-Za-z0-9+_.-]+@(.+)$"
password:
type: string
minLength: 7
type: object
CreateUserResponse:
description: represents the response to a User creation
required:
- access_token
- user_id
properties:
access_token:
type: string
user_id:
format: int64
type: integer
type: object
`)
spec.WriteString(tc.componentExamples)
loader := NewLoader()
doc, err := loader.LoadFromData(spec.Bytes())
require.NoError(t, err)
if testOption.disableExamplesValidation {
err = doc.Validate(loader.Context, DisableExamplesValidation())
} else {
err = doc.Validate(loader.Context)
}
if tc.errContains != "" {
require.Error(t, err)
require.ErrorContains(t, err, tc.errContains)
} else {
require.NoError(t, err)
}
})
}
})
}
}
kin-openapi-0.124.0/openapi3/extension.go 0000664 0000000 0000000 00000001124 14604223742 0020153 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"fmt"
"sort"
"strings"
)
func validateExtensions(ctx context.Context, extensions map[string]interface{}) error { // FIXME: newtype + Validate(...)
allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed
var unknowns []string
for k := range extensions {
if strings.HasPrefix(k, "x-") {
continue
}
if allowed != nil {
if _, ok := allowed[k]; ok {
continue
}
}
unknowns = append(unknowns, k)
}
if len(unknowns) != 0 {
sort.Strings(unknowns)
return fmt.Errorf("extra sibling fields: %+v", unknowns)
}
return nil
}
kin-openapi-0.124.0/openapi3/external_docs.go 0000664 0000000 0000000 00000003257 14604223742 0021002 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/url"
)
// ExternalDocs is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#external-documentation-object
type ExternalDocs struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
URL string `json:"url,omitempty" yaml:"url,omitempty"`
}
// MarshalJSON returns the JSON encoding of ExternalDocs.
func (e ExternalDocs) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{}, 2+len(e.Extensions))
for k, v := range e.Extensions {
m[k] = v
}
if x := e.Description; x != "" {
m["description"] = x
}
if x := e.URL; x != "" {
m["url"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets ExternalDocs to a copy of data.
func (e *ExternalDocs) UnmarshalJSON(data []byte) error {
type ExternalDocsBis ExternalDocs
var x ExternalDocsBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "description")
delete(x.Extensions, "url")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*e = ExternalDocs(x)
return nil
}
// Validate returns an error if ExternalDocs does not comply with the OpenAPI spec.
func (e *ExternalDocs) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if e.URL == "" {
return errors.New("url is required")
}
if _, err := url.Parse(e.URL); err != nil {
return fmt.Errorf("url is incorrect: %w", err)
}
return validateExtensions(ctx, e.Extensions)
}
kin-openapi-0.124.0/openapi3/external_docs_test.go 0000664 0000000 0000000 00000001617 14604223742 0022037 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"testing"
"github.com/stretchr/testify/require"
)
func TestExternalDocs_Validate(t *testing.T) {
tests := []struct {
name string
extDocs *ExternalDocs
expectedErr string
}{
{
name: "url is missing",
extDocs: &ExternalDocs{},
expectedErr: "url is required",
},
{
name: "url is incorrect",
extDocs: &ExternalDocs{URL: "ht tps://example.com"},
expectedErr: `url is incorrect: parse "ht tps://example.com": first path segment in URL cannot contain colon`,
},
{
name: "ok",
extDocs: &ExternalDocs{URL: "https://example.com"},
},
}
for i := range tests {
tt := tests[i]
t.Run(tt.name, func(t *testing.T) {
err := tt.extDocs.Validate(context.Background())
if tt.expectedErr != "" {
require.EqualError(t, err, tt.expectedErr)
} else {
require.NoError(t, err)
}
})
}
}
kin-openapi-0.124.0/openapi3/header.go 0000664 0000000 0000000 00000005600 14604223742 0017372 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"errors"
"fmt"
"github.com/go-openapi/jsonpointer"
)
// Header is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#header-object
type Header struct {
Parameter
}
var _ jsonpointer.JSONPointable = (*Header)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (header Header) JSONLookup(token string) (interface{}, error) {
return header.Parameter.JSONLookup(token)
}
// MarshalJSON returns the JSON encoding of Header.
func (header Header) MarshalJSON() ([]byte, error) {
return header.Parameter.MarshalJSON()
}
// UnmarshalJSON sets Header to a copy of data.
func (header *Header) UnmarshalJSON(data []byte) error {
return header.Parameter.UnmarshalJSON(data)
}
// MarshalYAML returns the JSON encoding of Header.
func (header Header) MarshalYAML() (interface{}, error) {
return header.Parameter, nil
}
// SerializationMethod returns a header's serialization method.
func (header *Header) SerializationMethod() (*SerializationMethod, error) {
style := header.Style
if style == "" {
style = SerializationSimple
}
explode := false
if header.Explode != nil {
explode = *header.Explode
}
return &SerializationMethod{Style: style, Explode: explode}, nil
}
// Validate returns an error if Header does not comply with the OpenAPI spec.
func (header *Header) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if header.Name != "" {
return errors.New("header 'name' MUST NOT be specified, it is given in the corresponding headers map")
}
if header.In != "" {
return errors.New("header 'in' MUST NOT be specified, it is implicitly in header")
}
// Validate a parameter's serialization method.
sm, err := header.SerializationMethod()
if err != nil {
return err
}
if smSupported := false ||
sm.Style == SerializationSimple && !sm.Explode ||
sm.Style == SerializationSimple && sm.Explode; !smSupported {
e := fmt.Errorf("serialization method with style=%q and explode=%v is not supported by a header parameter", sm.Style, sm.Explode)
return fmt.Errorf("header schema is invalid: %w", e)
}
if (header.Schema == nil) == (len(header.Content) == 0) {
e := fmt.Errorf("parameter must contain exactly one of content and schema: %v", header)
return fmt.Errorf("header schema is invalid: %w", e)
}
if schema := header.Schema; schema != nil {
if err := schema.Validate(ctx); err != nil {
return fmt.Errorf("header schema is invalid: %w", err)
}
}
if content := header.Content; content != nil {
e := errors.New("parameter content must only contain one entry")
if len(content) > 1 {
return fmt.Errorf("header content is invalid: %w", e)
}
if err := content.Validate(ctx); err != nil {
return fmt.Errorf("header content is invalid: %w", err)
}
}
return nil
}
kin-openapi-0.124.0/openapi3/helpers.go 0000664 0000000 0000000 00000002243 14604223742 0017604 0 ustar 00root root 0000000 0000000 package openapi3
import (
"fmt"
"regexp"
)
const identifierPattern = `^[a-zA-Z0-9._-]+$`
// IdentifierRegExp verifies whether Component object key matches 'identifierPattern' pattern, according to OpenAPI v3.x.
// However, to be able supporting legacy OpenAPI v2.x, there is a need to customize above pattern in order not to fail
// converted v2-v3 validation
var IdentifierRegExp = regexp.MustCompile(identifierPattern)
// ValidateIdentifier returns an error if the given component name does not match IdentifierRegExp.
func ValidateIdentifier(value string) error {
if IdentifierRegExp.MatchString(value) {
return nil
}
return fmt.Errorf("identifier %q is not supported by OpenAPIv3 standard (regexp: %q)", value, identifierPattern)
}
// Float64Ptr is a helper for defining OpenAPI schemas.
func Float64Ptr(value float64) *float64 {
return &value
}
// BoolPtr is a helper for defining OpenAPI schemas.
func BoolPtr(value bool) *bool {
return &value
}
// Int64Ptr is a helper for defining OpenAPI schemas.
func Int64Ptr(value int64) *int64 {
return &value
}
// Uint64Ptr is a helper for defining OpenAPI schemas.
func Uint64Ptr(value uint64) *uint64 {
return &value
}
kin-openapi-0.124.0/openapi3/info.go 0000664 0000000 0000000 00000004725 14604223742 0017104 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"encoding/json"
"errors"
)
// Info is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#info-object
type Info struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Title string `json:"title" yaml:"title"` // Required
Description string `json:"description,omitempty" yaml:"description,omitempty"`
TermsOfService string `json:"termsOfService,omitempty" yaml:"termsOfService,omitempty"`
Contact *Contact `json:"contact,omitempty" yaml:"contact,omitempty"`
License *License `json:"license,omitempty" yaml:"license,omitempty"`
Version string `json:"version" yaml:"version"` // Required
}
// MarshalJSON returns the JSON encoding of Info.
func (info Info) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{}, 6+len(info.Extensions))
for k, v := range info.Extensions {
m[k] = v
}
m["title"] = info.Title
if x := info.Description; x != "" {
m["description"] = x
}
if x := info.TermsOfService; x != "" {
m["termsOfService"] = x
}
if x := info.Contact; x != nil {
m["contact"] = x
}
if x := info.License; x != nil {
m["license"] = x
}
m["version"] = info.Version
return json.Marshal(m)
}
// UnmarshalJSON sets Info to a copy of data.
func (info *Info) UnmarshalJSON(data []byte) error {
type InfoBis Info
var x InfoBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "title")
delete(x.Extensions, "description")
delete(x.Extensions, "termsOfService")
delete(x.Extensions, "contact")
delete(x.Extensions, "license")
delete(x.Extensions, "version")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*info = Info(x)
return nil
}
// Validate returns an error if Info does not comply with the OpenAPI spec.
func (info *Info) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if contact := info.Contact; contact != nil {
if err := contact.Validate(ctx); err != nil {
return err
}
}
if license := info.License; license != nil {
if err := license.Validate(ctx); err != nil {
return err
}
}
if info.Version == "" {
return errors.New("value of version must be a non-empty string")
}
if info.Title == "" {
return errors.New("value of title must be a non-empty string")
}
return validateExtensions(ctx, info.Extensions)
}
kin-openapi-0.124.0/openapi3/internalize_refs.go 0000664 0000000 0000000 00000033057 14604223742 0021514 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"path/filepath"
"strings"
)
type RefNameResolver func(string) string
// DefaultRefResolver is a default implementation of refNameResolver for the
// InternalizeRefs function.
//
// If a reference points to an element inside a document, it returns the last
// element in the reference using filepath.Base. Otherwise if the reference points
// to a file, it returns the file name trimmed of all extensions.
func DefaultRefNameResolver(ref string) string {
if ref == "" {
return ""
}
split := strings.SplitN(ref, "#", 2)
if len(split) == 2 {
return filepath.Base(split[1])
}
ref = split[0]
for ext := filepath.Ext(ref); len(ext) > 0; ext = filepath.Ext(ref) {
ref = strings.TrimSuffix(ref, ext)
}
return filepath.Base(ref)
}
func schemaNames(s Schemas) []string {
out := make([]string, 0, len(s))
for i := range s {
out = append(out, i)
}
return out
}
func parametersMapNames(s ParametersMap) []string {
out := make([]string, 0, len(s))
for i := range s {
out = append(out, i)
}
return out
}
func isExternalRef(ref string, parentIsExternal bool) bool {
return ref != "" && (!strings.HasPrefix(ref, "#/components/") || parentIsExternal)
}
func (doc *T) addSchemaToSpec(s *SchemaRef, refNameResolver RefNameResolver, parentIsExternal bool) bool {
if s == nil || !isExternalRef(s.Ref, parentIsExternal) {
return false
}
name := refNameResolver(s.Ref)
if doc.Components != nil {
if _, ok := doc.Components.Schemas[name]; ok {
s.Ref = "#/components/schemas/" + name
return true
}
}
if doc.Components == nil {
doc.Components = &Components{}
}
if doc.Components.Schemas == nil {
doc.Components.Schemas = make(Schemas)
}
doc.Components.Schemas[name] = s.Value.NewRef()
s.Ref = "#/components/schemas/" + name
return true
}
func (doc *T) addParameterToSpec(p *ParameterRef, refNameResolver RefNameResolver, parentIsExternal bool) bool {
if p == nil || !isExternalRef(p.Ref, parentIsExternal) {
return false
}
name := refNameResolver(p.Ref)
if doc.Components != nil {
if _, ok := doc.Components.Parameters[name]; ok {
p.Ref = "#/components/parameters/" + name
return true
}
}
if doc.Components == nil {
doc.Components = &Components{}
}
if doc.Components.Parameters == nil {
doc.Components.Parameters = make(ParametersMap)
}
doc.Components.Parameters[name] = &ParameterRef{Value: p.Value}
p.Ref = "#/components/parameters/" + name
return true
}
func (doc *T) addHeaderToSpec(h *HeaderRef, refNameResolver RefNameResolver, parentIsExternal bool) bool {
if h == nil || !isExternalRef(h.Ref, parentIsExternal) {
return false
}
name := refNameResolver(h.Ref)
if doc.Components != nil {
if _, ok := doc.Components.Headers[name]; ok {
h.Ref = "#/components/headers/" + name
return true
}
}
if doc.Components == nil {
doc.Components = &Components{}
}
if doc.Components.Headers == nil {
doc.Components.Headers = make(Headers)
}
doc.Components.Headers[name] = &HeaderRef{Value: h.Value}
h.Ref = "#/components/headers/" + name
return true
}
func (doc *T) addRequestBodyToSpec(r *RequestBodyRef, refNameResolver RefNameResolver, parentIsExternal bool) bool {
if r == nil || !isExternalRef(r.Ref, parentIsExternal) {
return false
}
name := refNameResolver(r.Ref)
if doc.Components != nil {
if _, ok := doc.Components.RequestBodies[name]; ok {
r.Ref = "#/components/requestBodies/" + name
return true
}
}
if doc.Components == nil {
doc.Components = &Components{}
}
if doc.Components.RequestBodies == nil {
doc.Components.RequestBodies = make(RequestBodies)
}
doc.Components.RequestBodies[name] = &RequestBodyRef{Value: r.Value}
r.Ref = "#/components/requestBodies/" + name
return true
}
func (doc *T) addResponseToSpec(r *ResponseRef, refNameResolver RefNameResolver, parentIsExternal bool) bool {
if r == nil || !isExternalRef(r.Ref, parentIsExternal) {
return false
}
name := refNameResolver(r.Ref)
if doc.Components != nil {
if _, ok := doc.Components.Responses[name]; ok {
r.Ref = "#/components/responses/" + name
return true
}
}
if doc.Components == nil {
doc.Components = &Components{}
}
if doc.Components.Responses == nil {
doc.Components.Responses = make(ResponseBodies)
}
doc.Components.Responses[name] = &ResponseRef{Value: r.Value}
r.Ref = "#/components/responses/" + name
return true
}
func (doc *T) addSecuritySchemeToSpec(ss *SecuritySchemeRef, refNameResolver RefNameResolver, parentIsExternal bool) {
if ss == nil || !isExternalRef(ss.Ref, parentIsExternal) {
return
}
name := refNameResolver(ss.Ref)
if doc.Components != nil {
if _, ok := doc.Components.SecuritySchemes[name]; ok {
ss.Ref = "#/components/securitySchemes/" + name
return
}
}
if doc.Components == nil {
doc.Components = &Components{}
}
if doc.Components.SecuritySchemes == nil {
doc.Components.SecuritySchemes = make(SecuritySchemes)
}
doc.Components.SecuritySchemes[name] = &SecuritySchemeRef{Value: ss.Value}
ss.Ref = "#/components/securitySchemes/" + name
}
func (doc *T) addExampleToSpec(e *ExampleRef, refNameResolver RefNameResolver, parentIsExternal bool) {
if e == nil || !isExternalRef(e.Ref, parentIsExternal) {
return
}
name := refNameResolver(e.Ref)
if doc.Components != nil {
if _, ok := doc.Components.Examples[name]; ok {
e.Ref = "#/components/examples/" + name
return
}
}
if doc.Components == nil {
doc.Components = &Components{}
}
if doc.Components.Examples == nil {
doc.Components.Examples = make(Examples)
}
doc.Components.Examples[name] = &ExampleRef{Value: e.Value}
e.Ref = "#/components/examples/" + name
}
func (doc *T) addLinkToSpec(l *LinkRef, refNameResolver RefNameResolver, parentIsExternal bool) {
if l == nil || !isExternalRef(l.Ref, parentIsExternal) {
return
}
name := refNameResolver(l.Ref)
if doc.Components != nil {
if _, ok := doc.Components.Links[name]; ok {
l.Ref = "#/components/links/" + name
return
}
}
if doc.Components == nil {
doc.Components = &Components{}
}
if doc.Components.Links == nil {
doc.Components.Links = make(Links)
}
doc.Components.Links[name] = &LinkRef{Value: l.Value}
l.Ref = "#/components/links/" + name
}
func (doc *T) addCallbackToSpec(c *CallbackRef, refNameResolver RefNameResolver, parentIsExternal bool) bool {
if c == nil || !isExternalRef(c.Ref, parentIsExternal) {
return false
}
name := refNameResolver(c.Ref)
if doc.Components == nil {
doc.Components = &Components{}
}
if doc.Components.Callbacks == nil {
doc.Components.Callbacks = make(Callbacks)
}
c.Ref = "#/components/callbacks/" + name
doc.Components.Callbacks[name] = &CallbackRef{Value: c.Value}
return true
}
func (doc *T) derefSchema(s *Schema, refNameResolver RefNameResolver, parentIsExternal bool) {
if s == nil || doc.isVisitedSchema(s) {
return
}
for _, list := range []SchemaRefs{s.AllOf, s.AnyOf, s.OneOf} {
for _, s2 := range list {
isExternal := doc.addSchemaToSpec(s2, refNameResolver, parentIsExternal)
if s2 != nil {
doc.derefSchema(s2.Value, refNameResolver, isExternal || parentIsExternal)
}
}
}
for _, s2 := range s.Properties {
isExternal := doc.addSchemaToSpec(s2, refNameResolver, parentIsExternal)
if s2 != nil {
doc.derefSchema(s2.Value, refNameResolver, isExternal || parentIsExternal)
}
}
for _, ref := range []*SchemaRef{s.Not, s.AdditionalProperties.Schema, s.Items} {
isExternal := doc.addSchemaToSpec(ref, refNameResolver, parentIsExternal)
if ref != nil {
doc.derefSchema(ref.Value, refNameResolver, isExternal || parentIsExternal)
}
}
}
func (doc *T) derefHeaders(hs Headers, refNameResolver RefNameResolver, parentIsExternal bool) {
for _, h := range hs {
isExternal := doc.addHeaderToSpec(h, refNameResolver, parentIsExternal)
if doc.isVisitedHeader(h.Value) {
continue
}
doc.derefParameter(h.Value.Parameter, refNameResolver, parentIsExternal || isExternal)
}
}
func (doc *T) derefExamples(es Examples, refNameResolver RefNameResolver, parentIsExternal bool) {
for _, e := range es {
doc.addExampleToSpec(e, refNameResolver, parentIsExternal)
}
}
func (doc *T) derefContent(c Content, refNameResolver RefNameResolver, parentIsExternal bool) {
for _, mediatype := range c {
isExternal := doc.addSchemaToSpec(mediatype.Schema, refNameResolver, parentIsExternal)
if mediatype.Schema != nil {
doc.derefSchema(mediatype.Schema.Value, refNameResolver, isExternal || parentIsExternal)
}
doc.derefExamples(mediatype.Examples, refNameResolver, parentIsExternal)
for _, e := range mediatype.Encoding {
doc.derefHeaders(e.Headers, refNameResolver, parentIsExternal)
}
}
}
func (doc *T) derefLinks(ls Links, refNameResolver RefNameResolver, parentIsExternal bool) {
for _, l := range ls {
doc.addLinkToSpec(l, refNameResolver, parentIsExternal)
}
}
func (doc *T) derefResponse(r *ResponseRef, refNameResolver RefNameResolver, parentIsExternal bool) {
isExternal := doc.addResponseToSpec(r, refNameResolver, parentIsExternal)
if v := r.Value; v != nil {
doc.derefHeaders(v.Headers, refNameResolver, isExternal || parentIsExternal)
doc.derefContent(v.Content, refNameResolver, isExternal || parentIsExternal)
doc.derefLinks(v.Links, refNameResolver, isExternal || parentIsExternal)
}
}
func (doc *T) derefResponses(rs *Responses, refNameResolver RefNameResolver, parentIsExternal bool) {
doc.derefResponseBodies(rs.Map(), refNameResolver, parentIsExternal)
}
func (doc *T) derefResponseBodies(es ResponseBodies, refNameResolver RefNameResolver, parentIsExternal bool) {
for _, e := range es {
doc.derefResponse(e, refNameResolver, parentIsExternal)
}
}
func (doc *T) derefParameter(p Parameter, refNameResolver RefNameResolver, parentIsExternal bool) {
isExternal := doc.addSchemaToSpec(p.Schema, refNameResolver, parentIsExternal)
doc.derefContent(p.Content, refNameResolver, parentIsExternal)
if p.Schema != nil {
doc.derefSchema(p.Schema.Value, refNameResolver, isExternal || parentIsExternal)
}
}
func (doc *T) derefRequestBody(r RequestBody, refNameResolver RefNameResolver, parentIsExternal bool) {
doc.derefContent(r.Content, refNameResolver, parentIsExternal)
}
func (doc *T) derefPaths(paths map[string]*PathItem, refNameResolver RefNameResolver, parentIsExternal bool) {
for _, ops := range paths {
pathIsExternal := isExternalRef(ops.Ref, parentIsExternal)
// inline full operations
ops.Ref = ""
for _, param := range ops.Parameters {
doc.addParameterToSpec(param, refNameResolver, pathIsExternal)
}
for _, op := range ops.Operations() {
isExternal := doc.addRequestBodyToSpec(op.RequestBody, refNameResolver, pathIsExternal)
if op.RequestBody != nil && op.RequestBody.Value != nil {
doc.derefRequestBody(*op.RequestBody.Value, refNameResolver, pathIsExternal || isExternal)
}
for _, cb := range op.Callbacks {
isExternal := doc.addCallbackToSpec(cb, refNameResolver, pathIsExternal)
if cb.Value != nil {
cbValue := (*cb.Value).Map()
doc.derefPaths(cbValue, refNameResolver, pathIsExternal || isExternal)
}
}
doc.derefResponses(op.Responses, refNameResolver, pathIsExternal)
for _, param := range op.Parameters {
isExternal := doc.addParameterToSpec(param, refNameResolver, pathIsExternal)
if param.Value != nil {
doc.derefParameter(*param.Value, refNameResolver, pathIsExternal || isExternal)
}
}
}
}
}
// InternalizeRefs removes all references to external files from the spec and moves them
// to the components section.
//
// refNameResolver takes in references to returns a name to store the reference under locally.
// It MUST return a unique name for each reference type.
// A default implementation is provided that will suffice for most use cases. See the function
// documentation for more details.
//
// Example:
//
// doc.InternalizeRefs(context.Background(), nil)
func (doc *T) InternalizeRefs(ctx context.Context, refNameResolver func(ref string) string) {
doc.resetVisited()
if refNameResolver == nil {
refNameResolver = DefaultRefNameResolver
}
if components := doc.Components; components != nil {
names := schemaNames(components.Schemas)
for _, name := range names {
schema := components.Schemas[name]
isExternal := doc.addSchemaToSpec(schema, refNameResolver, false)
if schema != nil {
schema.Ref = "" // always dereference the top level
doc.derefSchema(schema.Value, refNameResolver, isExternal)
}
}
names = parametersMapNames(components.Parameters)
for _, name := range names {
p := components.Parameters[name]
isExternal := doc.addParameterToSpec(p, refNameResolver, false)
if p != nil && p.Value != nil {
p.Ref = "" // always dereference the top level
doc.derefParameter(*p.Value, refNameResolver, isExternal)
}
}
doc.derefHeaders(components.Headers, refNameResolver, false)
for _, req := range components.RequestBodies {
isExternal := doc.addRequestBodyToSpec(req, refNameResolver, false)
if req != nil && req.Value != nil {
req.Ref = "" // always dereference the top level
doc.derefRequestBody(*req.Value, refNameResolver, isExternal)
}
}
doc.derefResponseBodies(components.Responses, refNameResolver, false)
for _, ss := range components.SecuritySchemes {
doc.addSecuritySchemeToSpec(ss, refNameResolver, false)
}
doc.derefExamples(components.Examples, refNameResolver, false)
doc.derefLinks(components.Links, refNameResolver, false)
for _, cb := range components.Callbacks {
isExternal := doc.addCallbackToSpec(cb, refNameResolver, false)
if cb != nil && cb.Value != nil {
cb.Ref = "" // always dereference the top level
cbValue := (*cb.Value).Map()
doc.derefPaths(cbValue, refNameResolver, isExternal)
}
}
}
doc.derefPaths(doc.Paths.Map(), refNameResolver, false)
}
kin-openapi-0.124.0/openapi3/internalize_refs_test.go 0000664 0000000 0000000 00000003547 14604223742 0022554 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"os"
"regexp"
"testing"
"github.com/stretchr/testify/require"
)
func TestInternalizeRefs(t *testing.T) {
ctx := context.Background()
regexpRef := regexp.MustCompile(`"\$ref":`)
regexpRefInternal := regexp.MustCompile(`"\$ref":"#`)
tests := []struct {
filename string
}{
{"testdata/testref.openapi.yml"},
{"testdata/recursiveRef/openapi.yml"},
{"testdata/spec.yaml"},
{"testdata/callbacks.yml"},
{"testdata/issue831/testref.internalizepath.openapi.yml"},
}
for _, test := range tests {
t.Run(test.filename, func(t *testing.T) {
// Load in the reference spec from the testdata
sl := NewLoader()
sl.IsExternalRefsAllowed = true
doc, err := sl.LoadFromFile(test.filename)
require.NoError(t, err, "loading test file")
err = doc.Validate(ctx)
require.NoError(t, err, "validating spec")
// Internalize the references
doc.InternalizeRefs(ctx, nil)
// Validate the internalized spec
err = doc.Validate(ctx)
require.NoError(t, err, "validating internalized spec")
actual, err := doc.MarshalJSON()
require.NoError(t, err, "marshaling internalized spec")
// run a static check over the file, making sure each occurrence of a
// reference is followed by a #
numRefs := len(regexpRef.FindAll(actual, -1))
numInternalRefs := len(regexpRefInternal.FindAll(actual, -1))
require.Equal(t, numRefs, numInternalRefs, "checking all references are internal")
// load from actual, but with the path set to the current directory
doc2, err := sl.LoadFromData(actual)
require.NoError(t, err, "reloading spec")
err = doc2.Validate(ctx)
require.NoError(t, err, "validating reloaded spec")
// compare with expected
expected, err := os.ReadFile(test.filename + ".internalized.yml")
require.NoError(t, err)
require.JSONEq(t, string(expected), string(actual))
})
}
}
kin-openapi-0.124.0/openapi3/issue136_test.go 0000664 0000000 0000000 00000001547 14604223742 0020571 0 ustar 00root root 0000000 0000000 package openapi3
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestIssue136(t *testing.T) {
specf := func(dflt string) string {
return `
openapi: 3.0.2
info:
title: "Hello World REST APIs"
version: "1.0"
paths: {}
components:
schemas:
SomeSchema:
type: string
default: ` + dflt + `
`
}
for _, testcase := range []struct {
dflt, err string
}{
{
dflt: `"foo"`,
err: "",
},
{
dflt: `1`,
err: "invalid components: invalid schema default: value must be a string",
},
} {
t.Run(testcase.dflt, func(t *testing.T) {
spec := specf(testcase.dflt)
sl := NewLoader()
doc, err := sl.LoadFromData([]byte(spec))
require.NoError(t, err)
err = doc.Validate(sl.Context)
if testcase.err == "" {
require.NoError(t, err)
} else {
require.Error(t, err, testcase.err)
}
})
}
}
kin-openapi-0.124.0/openapi3/issue241_test.go 0000664 0000000 0000000 00000001101 14604223742 0020550 0 ustar 00root root 0000000 0000000 package openapi3_test
import (
"bytes"
"os"
"testing"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
"github.com/getkin/kin-openapi/openapi3"
)
func TestIssue241(t *testing.T) {
data, err := os.ReadFile("testdata/issue241.yml")
require.NoError(t, err)
loader := openapi3.NewLoader()
loader.IsExternalRefsAllowed = true
spec, err := loader.LoadFromData(data)
require.NoError(t, err)
var buf bytes.Buffer
enc := yaml.NewEncoder(&buf)
enc.SetIndent(2)
err = enc.Encode(spec)
require.NoError(t, err)
require.Equal(t, string(data), buf.String())
}
kin-openapi-0.124.0/openapi3/issue301_test.go 0000664 0000000 0000000 00000001470 14604223742 0020556 0 ustar 00root root 0000000 0000000 package openapi3
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestIssue301(t *testing.T) {
sl := NewLoader()
sl.IsExternalRefsAllowed = true
doc, err := sl.LoadFromFile("testdata/callbacks.yml")
require.NoError(t, err)
err = doc.Validate(sl.Context)
require.NoError(t, err)
require.Equal(t, &Types{"object"}, doc.
Paths.Value("/trans").
Post.Callbacks["transactionCallback"].Value.
Value("http://notificationServer.com?transactionId={$request.body#/id}&email={$request.body#/email}").
Post.RequestBody.Value.
Content["application/json"].Schema.Value.
Type)
require.Equal(t, &Types{"boolean"}, doc.
Paths.Value("/other").
Post.Callbacks["myEvent"].Value.
Value("{$request.query.queryUrl}").
Post.RequestBody.Value.
Content["application/json"].Schema.Value.
Type)
}
kin-openapi-0.124.0/openapi3/issue341_test.go 0000664 0000000 0000000 00000002611 14604223742 0020560 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"testing"
"github.com/stretchr/testify/require"
)
func TestIssue341(t *testing.T) {
sl := NewLoader()
sl.IsExternalRefsAllowed = true
doc, err := sl.LoadFromFile("testdata/main.yaml")
require.NoError(t, err)
err = doc.Validate(sl.Context)
require.NoError(t, err)
err = sl.ResolveRefsIn(doc, nil)
require.NoError(t, err)
bs, err := doc.MarshalJSON()
require.NoError(t, err)
require.JSONEq(t, `{
"info": {
"title": "test file",
"version": "n/a"
},
"openapi": "3.0.0",
"paths": {
"/testpath": {
"$ref": "testpath.yaml#/paths/~1testpath"
}
}
}`, string(bs))
require.Equal(t, &Types{"string"}, doc.
Paths.Value("/testpath").
Get.
Responses.Value("200").Value.
Content["application/json"].
Schema.Value.
Type)
doc.InternalizeRefs(context.Background(), nil)
bs, err = doc.MarshalJSON()
require.NoError(t, err)
require.JSONEq(t, `{
"components": {
"responses": {
"testpath_200_response": {
"content": {
"application/json": {
"schema": {
"type": "string"
}
}
},
"description": "a custom response"
}
}
},
"info": {
"title": "test file",
"version": "n/a"
},
"openapi": "3.0.0",
"paths": {
"/testpath": {
"get": {
"responses": {
"200": {
"$ref": "#/components/responses/testpath_200_response"
}
}
}
}
}
}`, string(bs))
}
kin-openapi-0.124.0/openapi3/issue344_test.go 0000664 0000000 0000000 00000000662 14604223742 0020567 0 ustar 00root root 0000000 0000000 package openapi3
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestIssue344(t *testing.T) {
sl := NewLoader()
sl.IsExternalRefsAllowed = true
doc, err := sl.LoadFromFile("testdata/spec.yaml")
require.NoError(t, err)
err = doc.Validate(sl.Context)
require.NoError(t, err)
require.Equal(t, &Types{"string"}, doc.Components.Schemas["Test"].Value.Properties["test"].Value.Properties["name"].Value.Type)
}
kin-openapi-0.124.0/openapi3/issue376_test.go 0000664 0000000 0000000 00000007102 14604223742 0020570 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"fmt"
"testing"
"github.com/stretchr/testify/require"
)
func TestIssue376(t *testing.T) {
spec := []byte(`
openapi: 3.0.0
components:
schemas:
schema1:
type: object
additionalProperties:
type: string
schema2:
type: object
properties:
prop:
$ref: '#/components/schemas/schema1/additionalProperties'
paths: {}
info:
title: An API
version: 1.2.3.4
`)
loader := NewLoader()
doc, err := loader.LoadFromData(spec)
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
require.Equal(t, "An API", doc.Info.Title)
require.Equal(t, 2, len(doc.Components.Schemas))
require.Equal(t, 0, doc.Paths.Len())
require.Equal(t, &Types{"string"}, doc.Components.Schemas["schema2"].Value.Properties["prop"].Value.Type)
}
func TestExclusiveValuesOfValuesAdditionalProperties(t *testing.T) {
schema := &Schema{
AdditionalProperties: AdditionalProperties{
Has: BoolPtr(false),
Schema: NewSchemaRef("", &Schema{}),
},
}
err := schema.Validate(context.Background())
require.ErrorContains(t, err, ` to both `)
schema = &Schema{
AdditionalProperties: AdditionalProperties{
Has: BoolPtr(false),
},
}
err = schema.Validate(context.Background())
require.NoError(t, err)
schema = &Schema{
AdditionalProperties: AdditionalProperties{
Schema: NewSchemaRef("", &Schema{}),
},
}
err = schema.Validate(context.Background())
require.NoError(t, err)
}
func TestMultijsonTagSerialization(t *testing.T) {
specYAML := []byte(`
openapi: 3.0.0
components:
schemas:
unset:
type: number
empty-object:
additionalProperties: {}
object:
additionalProperties: {type: string}
boolean:
additionalProperties: false
paths: {}
info:
title: An API
version: 1.2.3.4
`)
specJSON := []byte(`{
"openapi": "3.0.0",
"components": {
"schemas": {
"unset": {
"type": "number"
},
"empty-object": {
"additionalProperties": {
}
},
"object": {
"additionalProperties": {
"type": "string"
}
},
"boolean": {
"additionalProperties": false
}
}
},
"paths": {
},
"info": {
"title": "An API",
"version": "1.2.3.4"
}
}`)
for i, spec := range [][]byte{specJSON, specYAML} {
t.Run(fmt.Sprintf("spec%02d", i), func(t *testing.T) {
loader := NewLoader()
doc, err := loader.LoadFromData(spec)
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
for propName, propSchema := range doc.Components.Schemas {
t.Run(propName, func(t *testing.T) {
ap := propSchema.Value.AdditionalProperties.Schema
apa := propSchema.Value.AdditionalProperties.Has
apStr := ""
if ap != nil {
apStr = fmt.Sprintf("{Ref:%s Value.Type:%v}", (*ap).Ref, (*ap).Value.Type)
}
apaStr := ""
if apa != nil {
apaStr = fmt.Sprintf("%v", *apa)
}
encoded, err := propSchema.MarshalJSON()
require.NoError(t, err)
require.Equal(t, map[string]string{
"unset": `{"type":"number"}`,
"empty-object": `{"additionalProperties":{}}`,
"object": `{"additionalProperties":{"type":"string"}}`,
"boolean": `{"additionalProperties":false}`,
}[propName], string(encoded))
if propName == "unset" {
require.True(t, ap == nil && apa == nil)
return
}
require.Truef(t, (ap != nil && apa == nil) || (ap == nil && apa != nil),
"%s: isnil(%s) xor isnil(%s)", propName, apaStr, apStr)
})
}
})
}
}
kin-openapi-0.124.0/openapi3/issue382_test.go 0000664 0000000 0000000 00000000503 14604223742 0020563 0 ustar 00root root 0000000 0000000 package openapi3
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestOverridingGlobalParametersValidation(t *testing.T) {
loader := NewLoader()
doc, err := loader.LoadFromFile("testdata/Test_param_override.yml")
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
}
kin-openapi-0.124.0/openapi3/issue495_test.go 0000664 0000000 0000000 00000006150 14604223742 0020574 0 ustar 00root root 0000000 0000000 package openapi3
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestIssue495(t *testing.T) {
{
spec := []byte(`
openapi: 3.0.1
info:
version: v1
title: Products api
components:
schemas:
someSchema:
type: object
schemaArray:
type: array
minItems: 1
items:
$ref: '#'
paths:
/categories:
get:
responses:
'200':
description: ''
content:
application/json:
schema:
properties:
allOf:
$ref: '#/components/schemas/schemaArray'
`[1:])
sl := NewLoader()
doc, err := sl.LoadFromData(spec)
require.NoError(t, err)
err = doc.Validate(sl.Context)
require.EqualError(t, err, `invalid components: schema "schemaArray": found unresolved ref: "#"`)
}
spec := []byte(`
openapi: 3.0.1
info:
version: v1
title: Products api
components:
schemas:
someSchema:
type: object
schemaArray:
type: array
minItems: 1
items:
$ref: '#/components/schemas/someSchema'
paths:
/categories:
get:
responses:
'200':
description: ''
content:
application/json:
schema:
properties:
allOf:
$ref: '#/components/schemas/schemaArray'
`[1:])
sl := NewLoader()
doc, err := sl.LoadFromData(spec)
require.NoError(t, err)
err = doc.Validate(sl.Context)
require.NoError(t, err)
require.Equal(t, &Schema{Type: &Types{"object"}}, doc.Components.Schemas["schemaArray"].Value.Items.Value)
}
func TestIssue495WithDraft04(t *testing.T) {
spec := []byte(`
openapi: 3.0.1
servers:
- url: http://localhost:5000
info:
version: v1
title: Products api
contact:
name: me
email: me@github.com
description: This is a sample
paths:
/categories:
get:
summary: Provides the available categories for the store
operationId: list-categories
responses:
'200':
description: this is a desc
content:
application/json:
schema:
$ref: http://json-schema.org/draft-04/schema
`[1:])
sl := NewLoader()
sl.IsExternalRefsAllowed = true
doc, err := sl.LoadFromData(spec)
require.NoError(t, err)
err = doc.Validate(sl.Context)
require.ErrorContains(t, err, `found unresolved ref: "#"`)
}
func TestIssue495WithDraft04Bis(t *testing.T) {
spec := []byte(`
openapi: 3.0.1
servers:
- url: http://localhost:5000
info:
version: v1
title: Products api
contact:
name: me
email: me@github.com
description: This is a sample
paths:
/categories:
get:
summary: Provides the available categories for the store
operationId: list-categories
responses:
'200':
description: this is a desc
content:
application/json:
schema:
$ref: testdata/draft04.yml
`[1:])
sl := NewLoader()
sl.IsExternalRefsAllowed = true
doc, err := sl.LoadFromData(spec)
require.NoError(t, err)
err = doc.Validate(sl.Context)
require.ErrorContains(t, err, `found unresolved ref: "#"`)
}
kin-openapi-0.124.0/openapi3/issue513_test.go 0000664 0000000 0000000 00000013310 14604223742 0020557 0 ustar 00root root 0000000 0000000 package openapi3
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
)
func TestExtraSiblingsInRemoteRef(t *testing.T) {
spec := []byte(`
openapi: 3.0.1
servers:
- url: http://localhost:5000
info:
version: v1
title: Products api
contact:
name: me
email: me@github.com
description: This is a sample
paths:
/categories:
get:
summary: Provides the available categories for the store
operationId: list-categories
responses:
'200':
description: this is a desc
content:
application/json:
schema:
$ref: http://schemas.sentex.io/store/categories.json
`[1:])
// When that site fails to respond:
// see https://github.com/getkin/kin-openapi/issues/495
// http://schemas.sentex.io/store/categories.json
// {
// "$id": "http://schemas.sentex.io/store/categories.json",
// "$schema": "http://json-schema.org/draft-07/schema#",
// "description": "array of category strings",
// "type": "array",
// "items": {
// "allOf": [
// {
// "$ref": "http://schemas.sentex.io/store/category.json"
// }
// ]
// }
// }
// http://schemas.sentex.io/store/category.json
// {
// "$id": "http://schemas.sentex.io/store/category.json",
// "$schema": "http://json-schema.org/draft-07/schema#",
// "description": "category name for products",
// "type": "string",
// "pattern": "^[A-Za-z0-9\\-]+$",
// "minimum": 1,
// "maximum": 30
// }
sl := NewLoader()
sl.IsExternalRefsAllowed = true
doc, err := sl.LoadFromData(spec)
require.NoError(t, err)
err = doc.Validate(sl.Context, AllowExtraSiblingFields("$id", "$schema"))
require.NoError(t, err)
}
func TestIssue513OKWithExtension(t *testing.T) {
spec := `
openapi: "3.0.3"
info:
title: 'My app'
version: 1.0.0
description: 'An API'
paths:
/v1/operation:
delete:
summary: Delete something
responses:
200:
description: Success
default:
description: '* **400** - Bad Request'
x-my-extension: {val: ue}
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
components:
schemas:
Error:
type: object
description: An error response body.
properties:
message:
description: A detailed message describing the error.
type: string
`[1:]
sl := NewLoader()
doc, err := sl.LoadFromData([]byte(spec))
require.NoError(t, err)
err = doc.Validate(sl.Context)
require.NoError(t, err)
data, err := json.Marshal(doc)
require.NoError(t, err)
require.Contains(t, string(data), `x-my-extension`)
}
func TestIssue513KOHasExtraFieldSchema(t *testing.T) {
spec := `
openapi: "3.0.3"
info:
title: 'My app'
version: 1.0.0
description: 'An API'
paths:
/v1/operation:
delete:
summary: Delete something
responses:
200:
description: Success
default:
description: '* **400** - Bad Request'
x-my-extension: {val: ue}
# Notice here schema is invalid. It should instead be:
# content:
# application/json:
# schema:
# $ref: '#/components/schemas/Error'
schema:
$ref: '#/components/schemas/Error'
components:
schemas:
Error:
type: object
description: An error response body.
properties:
message:
description: A detailed message describing the error.
type: string
`[1:]
sl := NewLoader()
doc, err := sl.LoadFromData([]byte(spec))
require.NoError(t, err)
require.Contains(t, doc.Paths.Value("/v1/operation").Delete.Responses.Default().Value.Extensions, `x-my-extension`)
err = doc.Validate(sl.Context)
require.ErrorContains(t, err, `extra sibling fields: [schema]`)
}
func TestIssue513KOMixesRefAlongWithOtherFieldsDisallowed(t *testing.T) {
spec := `
openapi: "3.0.3"
info:
title: 'My app'
version: 1.0.0
description: 'An API'
paths:
/v1/operation:
delete:
summary: Delete something
responses:
200:
description: A sibling field that the spec says is ignored
$ref: '#/components/responses/SomeResponseBody'
components:
responses:
SomeResponseBody:
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
schemas:
Error:
type: object
description: An error response body.
properties:
message:
description: A detailed message describing the error.
type: string
`[1:]
sl := NewLoader()
doc, err := sl.LoadFromData([]byte(spec))
require.NoError(t, err)
err = doc.Validate(sl.Context)
require.ErrorContains(t, err, `extra sibling fields: [description]`)
}
func TestIssue513KOMixesRefAlongWithOtherFieldsAllowed(t *testing.T) {
spec := `
openapi: "3.0.3"
info:
title: 'My app'
version: 1.0.0
description: 'An API'
paths:
/v1/operation:
delete:
summary: Delete something
responses:
200:
description: A sibling field that the spec says is ignored
$ref: '#/components/responses/SomeResponseBody'
components:
responses:
SomeResponseBody:
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
schemas:
Error:
type: object
description: An error response body.
properties:
message:
description: A detailed message describing the error.
type: string
`[1:]
sl := NewLoader()
doc, err := sl.LoadFromData([]byte(spec))
require.NoError(t, err)
err = doc.Validate(sl.Context, AllowExtraSiblingFields("description"))
require.NoError(t, err)
}
kin-openapi-0.124.0/openapi3/issue542_test.go 0000664 0000000 0000000 00000001073 14604223742 0020564 0 ustar 00root root 0000000 0000000 package openapi3
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestIssue542(t *testing.T) {
spec := []byte(`
openapi: '3.0.0'
info:
version: '1.0.0'
title: Swagger Petstore
license:
name: MIT
servers:
- url: http://petstore.swagger.io/v1
paths: {}
components:
schemas:
Cat:
anyOf:
- $ref: '#/components/schemas/Kitten'
- type: object
Kitten:
type: string
`[1:])
sl := NewLoader()
doc, err := sl.LoadFromData(spec)
require.NoError(t, err)
err = doc.Validate(sl.Context)
require.NoError(t, err)
}
kin-openapi-0.124.0/openapi3/issue570_test.go 0000664 0000000 0000000 00000000374 14604223742 0020570 0 ustar 00root root 0000000 0000000 package openapi3
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestIssue570(t *testing.T) {
loader := NewLoader()
_, err := loader.LoadFromFile("testdata/issue570.json")
require.ErrorContains(t, err, CircularReferenceError)
}
kin-openapi-0.124.0/openapi3/issue594_test.go 0000664 0000000 0000000 00000001362 14604223742 0020574 0 ustar 00root root 0000000 0000000 package openapi3_test
import (
"net/url"
"testing"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
)
func TestIssue594(t *testing.T) {
uri, err := url.Parse("https://raw.githubusercontent.com/sendgrid/sendgrid-oai/c3aaa432b769faa47285166aca17c7ed2ea71787/oai_v3_stoplight.json")
require.NoError(t, err)
sl := openapi3.NewLoader()
var doc *openapi3.T
if false {
doc, err = sl.LoadFromURI(uri)
} else {
doc, err = sl.LoadFromFile("testdata/oai_v3_stoplight.json")
}
require.NoError(t, err)
doc.Info.Version = "1.2.3"
doc.Paths.Value("/marketing/contacts/search/emails").Post = nil
doc.Components.Schemas["full-segment"].Value.Example = nil
err = doc.Validate(sl.Context)
require.NoError(t, err)
}
kin-openapi-0.124.0/openapi3/issue601_test.go 0000664 0000000 0000000 00000002000 14604223742 0020547 0 ustar 00root root 0000000 0000000 package openapi3
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestIssue601(t *testing.T) {
// Document is invalid: first validation error returned is because
// schema:
// example: {key: value}
// is not how schema examples are defined (but how components' examples are defined. Components are maps.)
// Correct code should be:
// schema: {example: value}
sl := NewLoader()
doc, err := sl.LoadFromFile("testdata/lxkns.yaml")
require.NoError(t, err)
err = doc.Validate(sl.Context)
require.ErrorContains(t, err, `invalid components: schema "DiscoveryResult": invalid example: Error at "/type": property "type" is missing`)
require.ErrorContains(t, err, `| Error at "/nsid": property "nsid" is missing`)
err = doc.Validate(sl.Context, DisableExamplesValidation())
require.NoError(t, err)
// Now let's remove all the invalid parts
for _, schema := range doc.Components.Schemas {
schema.Value.Example = nil
}
err = doc.Validate(sl.Context)
require.NoError(t, err)
}
kin-openapi-0.124.0/openapi3/issue615_test.go 0000664 0000000 0000000 00000001622 14604223742 0020565 0 ustar 00root root 0000000 0000000 package openapi3_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
)
func TestIssue615(t *testing.T) {
{
var old int
old, openapi3.CircularReferenceCounter = openapi3.CircularReferenceCounter, 1
defer func() { openapi3.CircularReferenceCounter = old }()
loader := openapi3.NewLoader()
loader.IsExternalRefsAllowed = true
_, err := loader.LoadFromFile("testdata/recursiveRef/issue615.yml")
require.ErrorContains(t, err, openapi3.CircularReferenceError)
}
var old int
old, openapi3.CircularReferenceCounter = openapi3.CircularReferenceCounter, 4
defer func() { openapi3.CircularReferenceCounter = old }()
loader := openapi3.NewLoader()
loader.IsExternalRefsAllowed = true
doc, err := loader.LoadFromFile("testdata/recursiveRef/issue615.yml")
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
}
kin-openapi-0.124.0/openapi3/issue618_test.go 0000664 0000000 0000000 00000001445 14604223742 0020573 0 ustar 00root root 0000000 0000000 package openapi3
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestIssue618(t *testing.T) {
spec := `
openapi: 3.0.0
info:
title: foo
version: 0.0.0
paths:
/foo:
get:
responses:
'200':
description: Some description value text
content:
application/json:
schema:
$ref: ./testdata/schema618.yml#/components/schemas/JournalEntry
`[1:]
loader := NewLoader()
loader.IsExternalRefsAllowed = true
ctx := loader.Context
doc, err := loader.LoadFromData([]byte(spec))
require.NoError(t, err)
doc.InternalizeRefs(ctx, nil)
require.Contains(t, doc.Components.Schemas, "JournalEntry")
require.Contains(t, doc.Components.Schemas, "Record")
require.Contains(t, doc.Components.Schemas, "Account")
}
kin-openapi-0.124.0/openapi3/issue638_test.go 0000664 0000000 0000000 00000001147 14604223742 0020574 0 ustar 00root root 0000000 0000000 package openapi3
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestIssue638(t *testing.T) {
for i := 0; i < 50; i++ {
loader := NewLoader()
loader.IsExternalRefsAllowed = true
// This path affects the occurrence of the issue #638.
// ../openapi3/testdata/issue638/test1.yaml : reproduce
// ./testdata/issue638/test1.yaml : not reproduce
// testdata/issue638/test1.yaml : reproduce
doc, err := loader.LoadFromFile("testdata/issue638/test1.yaml")
require.NoError(t, err)
require.Equal(t, &Types{"int"}, doc.Components.Schemas["test1d"].Value.Type)
}
}
kin-openapi-0.124.0/openapi3/issue652_test.go 0000664 0000000 0000000 00000001536 14604223742 0020572 0 ustar 00root root 0000000 0000000 package openapi3_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
)
func TestIssue652(t *testing.T) {
loader := openapi3.NewLoader()
loader.IsExternalRefsAllowed = true
// Test checks that no slice bounds out of range error occurs while loading
// from file that contains reference to file in the parent directory.
require.NotPanics(t, func() {
const schemaName = "ReferenceToParentDirectory"
spec, err := loader.LoadFromFile("testdata/issue652/nested/schema.yml")
require.NoError(t, err)
require.Contains(t, spec.Components.Schemas, schemaName)
schema := spec.Components.Schemas[schemaName]
assert.Equal(t, schema.Ref, "../definitions.yml#/components/schemas/TestSchema")
assert.Equal(t, schema.Value.Type, &openapi3.Types{"string"})
})
}
kin-openapi-0.124.0/openapi3/issue657_test.go 0000664 0000000 0000000 00000003303 14604223742 0020571 0 ustar 00root root 0000000 0000000 package openapi3_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
)
func TestOneOf_Warning_Errors(t *testing.T) {
t.Parallel()
loader := openapi3.NewLoader()
spec := `
components:
schemas:
Something:
type: object
properties:
field:
title: Some field
oneOf:
- title: First rule
type: string
minLength: 10
maxLength: 10
- title: Second rule
type: string
minLength: 15
maxLength: 15
`[1:]
doc, err := loader.LoadFromData([]byte(spec))
require.NoError(t, err)
tests := [...]struct {
name string
value string
checkErr require.ErrorAssertionFunc
}{
{
name: "valid value",
value: "ABCDE01234",
checkErr: require.NoError,
},
{
name: "valid value",
value: "ABCDE0123456789",
checkErr: require.NoError,
},
{
name: "no valid value",
value: "ABCDE",
checkErr: func(t require.TestingT, err error, i ...interface{}) {
require.ErrorContains(t, err, "doesn't match schema due to: minimum string length is 10")
wErr := &openapi3.MultiError{}
require.ErrorAs(t, err, wErr)
require.Len(t, *wErr, 2)
require.Equal(t, "minimum string length is 10", (*wErr)[0].(*openapi3.SchemaError).Reason)
require.Equal(t, "minimum string length is 15", (*wErr)[1].(*openapi3.SchemaError).Reason)
},
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
err = doc.Components.Schemas["Something"].Value.Properties["field"].Value.VisitJSON(test.value)
test.checkErr(t, err)
})
}
}
kin-openapi-0.124.0/openapi3/issue689_test.go 0000664 0000000 0000000 00000007273 14604223742 0020610 0 ustar 00root root 0000000 0000000 package openapi3_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
)
func TestIssue689(t *testing.T) {
t.Parallel()
tests := [...]struct {
name string
schema *openapi3.Schema
value map[string]interface{}
opts []openapi3.SchemaValidationOption
checkErr require.ErrorAssertionFunc
}{
// read-only
{
name: "read-only property succeeds when read-only validation is disabled",
schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
"foo": {Type: &openapi3.Types{"boolean"}, ReadOnly: true}}),
value: map[string]interface{}{"foo": true},
opts: []openapi3.SchemaValidationOption{
openapi3.VisitAsRequest(),
openapi3.DisableReadOnlyValidation()},
checkErr: require.NoError,
},
{
name: "non read-only property succeeds when read-only validation is disabled",
schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
"foo": {Type: &openapi3.Types{"boolean"}, ReadOnly: false}}),
opts: []openapi3.SchemaValidationOption{
openapi3.VisitAsRequest()},
value: map[string]interface{}{"foo": true},
checkErr: require.NoError,
},
{
name: "read-only property fails when read-only validation is enabled",
schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
"foo": {Type: &openapi3.Types{"boolean"}, ReadOnly: true}}),
opts: []openapi3.SchemaValidationOption{
openapi3.VisitAsRequest()},
value: map[string]interface{}{"foo": true},
checkErr: require.Error,
},
{
name: "non read-only property succeeds when read-only validation is enabled",
schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
"foo": {Type: &openapi3.Types{"boolean"}, ReadOnly: false}}),
opts: []openapi3.SchemaValidationOption{
openapi3.VisitAsRequest()},
value: map[string]interface{}{"foo": true},
checkErr: require.NoError,
},
// write-only
{
name: "write-only property succeeds when write-only validation is disabled",
schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
"foo": {Type: &openapi3.Types{"boolean"}, WriteOnly: true}}),
value: map[string]interface{}{"foo": true},
opts: []openapi3.SchemaValidationOption{
openapi3.VisitAsResponse(),
openapi3.DisableWriteOnlyValidation()},
checkErr: require.NoError,
},
{
name: "non write-only property succeeds when write-only validation is disabled",
schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
"foo": {Type: &openapi3.Types{"boolean"}, WriteOnly: false}}),
opts: []openapi3.SchemaValidationOption{
openapi3.VisitAsResponse()},
value: map[string]interface{}{"foo": true},
checkErr: require.NoError,
},
{
name: "write-only property fails when write-only validation is enabled",
schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
"foo": {Type: &openapi3.Types{"boolean"}, WriteOnly: true}}),
opts: []openapi3.SchemaValidationOption{
openapi3.VisitAsResponse()},
value: map[string]interface{}{"foo": true},
checkErr: require.Error,
},
{
name: "non write-only property succeeds when write-only validation is enabled",
schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
"foo": {Type: &openapi3.Types{"boolean"}, WriteOnly: false}}),
opts: []openapi3.SchemaValidationOption{
openapi3.VisitAsResponse()},
value: map[string]interface{}{"foo": true},
checkErr: require.NoError,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
err := test.schema.VisitJSON(test.value, test.opts...)
test.checkErr(t, err)
})
}
}
kin-openapi-0.124.0/openapi3/issue697_test.go 0000664 0000000 0000000 00000000434 14604223742 0020577 0 ustar 00root root 0000000 0000000 package openapi3
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestIssue697(t *testing.T) {
loader := NewLoader()
doc, err := loader.LoadFromFile("testdata/issue697.yml")
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
}
kin-openapi-0.124.0/openapi3/issue735_test.go 0000664 0000000 0000000 00000014763 14604223742 0020602 0 ustar 00root root 0000000 0000000 package openapi3
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
)
type testCase struct {
name string
schema *Schema
value interface{}
extraNotContains []interface{}
options []SchemaValidationOption
}
func TestIssue735(t *testing.T) {
DefineStringFormat("uuid", FormatOfStringForUUIDOfRFC4122)
DefineStringFormat("email", FormatOfStringForEmail)
DefineIPv4Format()
DefineIPv6Format()
testCases := []testCase{
{
name: "type string",
schema: NewStringSchema(),
value: 42,
},
{
name: "type boolean",
schema: NewBoolSchema(),
value: 42,
},
{
name: "type integer",
schema: NewIntegerSchema(),
value: "foo",
},
{
name: "type number",
schema: NewFloat64Schema(),
value: "foo",
},
{
name: "type array",
schema: NewArraySchema(),
value: 42,
},
{
name: "type object",
schema: NewObjectSchema(),
value: 42,
},
{
name: "min",
schema: NewSchema().WithMin(100),
value: 42,
},
{
name: "max",
schema: NewSchema().WithMax(0),
value: 42,
},
{
name: "exclusive min",
schema: NewSchema().WithMin(100).WithExclusiveMin(true),
value: 42,
},
{
name: "exclusive max",
schema: NewSchema().WithMax(0).WithExclusiveMax(true),
value: 42,
},
{
name: "multiple of",
schema: &Schema{MultipleOf: Float64Ptr(5.0)},
value: 42,
},
{
name: "enum",
schema: NewSchema().WithEnum(3, 5),
value: 42,
},
{
name: "min length",
schema: NewSchema().WithMinLength(100),
value: "foo",
},
{
name: "max length",
schema: NewSchema().WithMaxLength(0),
value: "foo",
},
{
name: "pattern",
schema: NewSchema().WithPattern("[0-9]"),
value: "foo",
},
{
name: "items",
schema: NewSchema().WithItems(NewStringSchema()),
value: []interface{}{42},
extraNotContains: []interface{}{42},
},
{
name: "min items",
schema: NewSchema().WithMinItems(100),
value: []interface{}{42},
extraNotContains: []interface{}{42},
},
{
name: "max items",
schema: NewSchema().WithMaxItems(0),
value: []interface{}{42},
extraNotContains: []interface{}{42},
},
{
name: "unique items",
schema: NewSchema().WithUniqueItems(true),
value: []interface{}{42, 42},
extraNotContains: []interface{}{42},
},
{
name: "min properties",
schema: NewSchema().WithMinProperties(100),
value: map[string]interface{}{"foo": 42},
extraNotContains: []interface{}{42},
},
{
name: "max properties",
schema: NewSchema().WithMaxProperties(0),
value: map[string]interface{}{"foo": 42},
extraNotContains: []interface{}{42},
},
{
name: "additional properties other schema type",
schema: NewSchema().WithAdditionalProperties(NewStringSchema()),
value: map[string]interface{}{"foo": 42},
extraNotContains: []interface{}{42},
},
{
name: "additional properties false",
schema: &Schema{AdditionalProperties: AdditionalProperties{
Has: BoolPtr(false),
}},
value: map[string]interface{}{"foo": 42},
extraNotContains: []interface{}{42},
},
{
name: "invalid properties schema",
schema: NewSchema().WithProperties(map[string]*Schema{
"foo": NewStringSchema(),
}),
value: map[string]interface{}{"foo": 42},
extraNotContains: []interface{}{42},
},
// TODO: uncomment when https://github.com/getkin/kin-openapi/issues/502 is fixed
//{
// name: "read only properties",
// schema: NewSchema().WithProperties(map[string]*Schema{
// "foo": {ReadOnly: true},
// }).WithoutAdditionalProperties(),
// value: map[string]interface{}{"foo": 42},
// extraNotContains: []interface{}{42},
// options: []SchemaValidationOption{VisitAsRequest()},
//},
//{
// name: "write only properties",
// schema: NewSchema().WithProperties(map[string]*Schema{
// "foo": {WriteOnly: true},
// }).WithoutAdditionalProperties(),
// value: map[string]interface{}{"foo": 42},
// extraNotContains: []interface{}{42},
// options: []SchemaValidationOption{VisitAsResponse()},
//},
{
name: "required properties",
schema: &Schema{
Properties: Schemas{
"bar": NewStringSchema().NewRef(),
},
Required: []string{"bar"},
},
value: map[string]interface{}{"foo": 42},
extraNotContains: []interface{}{42},
},
{
name: "one of (matches more then one)",
schema: NewOneOfSchema(
&Schema{MultipleOf: Float64Ptr(6)},
&Schema{MultipleOf: Float64Ptr(7)},
),
value: 42,
},
{
name: "one of (no matches)",
schema: NewOneOfSchema(
&Schema{MultipleOf: Float64Ptr(5)},
&Schema{MultipleOf: Float64Ptr(10)},
),
value: 42,
},
{
name: "any of",
schema: NewAnyOfSchema(
&Schema{MultipleOf: Float64Ptr(5)},
&Schema{MultipleOf: Float64Ptr(10)},
),
value: 42,
},
{
name: "all of (match some)",
schema: NewAllOfSchema(
&Schema{MultipleOf: Float64Ptr(6)},
&Schema{MultipleOf: Float64Ptr(5)},
),
value: 42,
},
{
name: "all of (no match)",
schema: NewAllOfSchema(
&Schema{MultipleOf: Float64Ptr(10)},
&Schema{MultipleOf: Float64Ptr(5)},
),
value: 42,
},
{
name: "uuid format",
schema: NewUUIDSchema(),
value: "foo",
},
{
name: "date time format",
schema: NewDateTimeSchema(),
value: "foo",
},
{
name: "date format",
schema: NewSchema().WithFormat("date"),
value: "foo",
},
{
name: "ipv4 format",
schema: NewSchema().WithFormat("ipv4"),
value: "foo",
},
{
name: "ipv6 format",
schema: NewSchema().WithFormat("ipv6"),
value: "foo",
},
{
name: "email format",
schema: NewSchema().WithFormat("email"),
value: "foo",
},
{
name: "byte format",
schema: NewBytesSchema(),
value: "foo!",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := tc.schema.VisitJSON(tc.value, tc.options...)
var schemaError = &SchemaError{}
require.Error(t, err)
require.ErrorAs(t, err, &schemaError)
require.NotZero(t, schemaError.Reason)
require.NotContains(t, schemaError.Reason, fmt.Sprint(tc.value))
for _, extra := range tc.extraNotContains {
require.NotContains(t, schemaError.Reason, fmt.Sprint(extra))
}
})
}
}
kin-openapi-0.124.0/openapi3/issue741_test.go 0000664 0000000 0000000 00000002025 14604223742 0020563 0 ustar 00root root 0000000 0000000 package openapi3
import (
"fmt"
"net/http"
"net/http/httptest"
"sync"
"testing"
"github.com/stretchr/testify/require"
)
func TestIssue741(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
body := `{"openapi":"3.0.0","info":{"title":"MyAPI","version":"0.1","description":"An API"},"paths":{},"components":{"schemas":{"Foo":{"type":"string"}}}}`
_, err := w.Write([]byte(body))
if err != nil {
panic(err)
}
}))
defer ts.Close()
rootSpec := []byte(fmt.Sprintf(
`{"openapi":"3.0.0","info":{"title":"MyAPI","version":"0.1","description":"An API"},"paths":{},"components":{"schemas":{"Bar1":{"$ref":"%s#/components/schemas/Foo"}}}}`,
ts.URL,
))
wg := &sync.WaitGroup{}
n := 10
for i := 0; i < n; i++ {
wg.Add(1)
go func() {
defer wg.Done()
loader := NewLoader()
loader.IsExternalRefsAllowed = true
doc, err := loader.LoadFromData(rootSpec)
require.NoError(t, err)
require.NotNil(t, doc)
}()
}
wg.Wait()
}
kin-openapi-0.124.0/openapi3/issue746_test.go 0000664 0000000 0000000 00000001133 14604223742 0020567 0 ustar 00root root 0000000 0000000 package openapi3
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
)
func TestIssue746(t *testing.T) {
schema := &Schema{}
err := schema.UnmarshalJSON([]byte(`{"additionalProperties": false}`))
require.NoError(t, err)
var value interface{}
err = json.Unmarshal([]byte(`{"foo": "bar"}`), &value)
require.NoError(t, err)
err = schema.VisitJSON(value)
require.Error(t, err)
schemaErr := &SchemaError{}
require.ErrorAs(t, err, &schemaErr)
require.Equal(t, "properties", schemaErr.SchemaField)
require.Equal(t, `property "foo" is unsupported`, schemaErr.Reason)
}
kin-openapi-0.124.0/openapi3/issue753_test.go 0000664 0000000 0000000 00000001251 14604223742 0020566 0 ustar 00root root 0000000 0000000 package openapi3
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestIssue753(t *testing.T) {
loader := NewLoader()
doc, err := loader.LoadFromFile("testdata/issue753.yml")
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
require.NotNil(t, doc.
Paths.Value("/test1").
Post.Callbacks["callback1"].Value.
Value("{$request.body#/callback}").
Post.RequestBody.Value.
Content["application/json"].
Schema.Value)
require.NotNil(t, doc.
Paths.Value("/test2").
Post.Callbacks["callback2"].Value.
Value("{$request.body#/callback}").
Post.RequestBody.Value.
Content["application/json"].
Schema.Value)
}
kin-openapi-0.124.0/openapi3/issue759_test.go 0000664 0000000 0000000 00000001202 14604223742 0020570 0 ustar 00root root 0000000 0000000 package openapi3
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestIssue759(t *testing.T) {
spec := []byte(`
openapi: 3.0.0
info:
title: title
description: description
version: 0.0.0
paths:
/slash:
get:
responses:
"200":
# Ref should point to a response, not a schema
$ref: "#/components/schemas/UserStruct"
components:
schemas:
UserStruct:
type: object
`[1:])
loader := NewLoader()
doc, err := loader.LoadFromData(spec)
require.Nil(t, doc)
require.EqualError(t, err, `bad data in "#/components/schemas/UserStruct" (expecting ref to response object)`)
}
kin-openapi-0.124.0/openapi3/issue767_test.go 0000664 0000000 0000000 00000005320 14604223742 0020574 0 ustar 00root root 0000000 0000000 package openapi3_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
)
func TestIssue767(t *testing.T) {
t.Parallel()
tests := [...]struct {
name string
schema *openapi3.Schema
value map[string]interface{}
opts []openapi3.SchemaValidationOption
checkErr require.ErrorAssertionFunc
}{
{
name: "default values disabled should fail with minProps 1",
schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
"foo": {Type: &openapi3.Types{"boolean"}, Default: true}}).WithMinProperties(1),
value: map[string]interface{}{},
opts: []openapi3.SchemaValidationOption{
openapi3.VisitAsRequest(),
},
checkErr: require.Error,
},
{
name: "default values enabled should pass with minProps 1",
schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
"foo": {Type: &openapi3.Types{"boolean"}, Default: true}}).WithMinProperties(1),
value: map[string]interface{}{},
opts: []openapi3.SchemaValidationOption{
openapi3.VisitAsRequest(),
openapi3.DefaultsSet(func() {}),
},
checkErr: require.NoError,
},
{
name: "default values enabled should pass with minProps 2",
schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
"foo": {Type: &openapi3.Types{"boolean"}, Default: true},
"bar": {Type: &openapi3.Types{"boolean"}},
}).WithMinProperties(2),
value: map[string]interface{}{"bar": false},
opts: []openapi3.SchemaValidationOption{
openapi3.VisitAsRequest(),
openapi3.DefaultsSet(func() {}),
},
checkErr: require.NoError,
},
{
name: "default values enabled should fail with maxProps 1",
schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
"foo": {Type: &openapi3.Types{"boolean"}, Default: true},
"bar": {Type: &openapi3.Types{"boolean"}},
}).WithMaxProperties(1),
value: map[string]interface{}{"bar": false},
opts: []openapi3.SchemaValidationOption{
openapi3.VisitAsRequest(),
openapi3.DefaultsSet(func() {}),
},
checkErr: require.Error,
},
{
name: "default values disabled should pass with maxProps 1",
schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
"foo": {Type: &openapi3.Types{"boolean"}, Default: true},
"bar": {Type: &openapi3.Types{"boolean"}},
}).WithMaxProperties(1),
value: map[string]interface{}{"bar": false},
opts: []openapi3.SchemaValidationOption{
openapi3.VisitAsRequest(),
},
checkErr: require.NoError,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
err := test.schema.VisitJSON(test.value, test.opts...)
test.checkErr(t, err)
})
}
}
kin-openapi-0.124.0/openapi3/issue794_test.go 0000664 0000000 0000000 00000000437 14604223742 0020600 0 ustar 00root root 0000000 0000000 package openapi3
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestCrashOnLoad(t *testing.T) {
loader := NewLoader()
doc, err := loader.LoadFromFile("testdata/issue794.yml")
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
}
kin-openapi-0.124.0/openapi3/issue796_test.go 0000664 0000000 0000000 00000000715 14604223742 0020601 0 ustar 00root root 0000000 0000000 package openapi3
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestIssue796(t *testing.T) {
var old int
// Need to set CircularReferenceCounter to > 10
old, CircularReferenceCounter = CircularReferenceCounter, 20
defer func() { CircularReferenceCounter = old }()
loader := NewLoader()
doc, err := loader.LoadFromFile("testdata/issue796.yml")
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
}
kin-openapi-0.124.0/openapi3/issue819_test.go 0000664 0000000 0000000 00000001602 14604223742 0020571 0 ustar 00root root 0000000 0000000 package openapi3
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestIssue819ResponsesGetPatternedFields(t *testing.T) {
spec := `
openapi: "3.0.3"
info:
title: 'My app'
version: 1.0.0
description: 'An API'
paths:
/v1/operation:
get:
summary: Fetch something
responses:
2XX:
description: Success
content:
application/json:
schema:
type: object
description: An error response body.
`[1:]
sl := NewLoader()
doc, err := sl.LoadFromData([]byte(spec))
require.NoError(t, err)
err = doc.Validate(sl.Context)
require.NoError(t, err)
require.NotNil(t, doc.Paths.Value("/v1/operation").Get.Responses.Status(201))
require.Nil(t, doc.Paths.Value("/v1/operation").Get.Responses.Status(404))
require.Nil(t, doc.Paths.Value("/v1/operation").Get.Responses.Status(999))
}
kin-openapi-0.124.0/openapi3/issue883_test.go 0000664 0000000 0000000 00000004422 14604223742 0020575 0 ustar 00root root 0000000 0000000 package openapi3_test
import (
"testing"
invopopYaml "github.com/invopop/yaml"
"github.com/stretchr/testify/require"
v3 "gopkg.in/yaml.v3"
"github.com/getkin/kin-openapi/openapi3"
)
func TestIssue883(t *testing.T) {
spec := `
openapi: '3.0.0'
info:
version: '1.0.0'
title: Swagger Petstore
license:
name: MIT
servers:
- url: http://petstore.swagger.io/v1
paths:
/simple:
get:
summary: A simple GET request
responses:
'200':
description: OK
`[1:]
sl := openapi3.NewLoader()
doc, err := sl.LoadFromData([]byte(spec))
require.NoError(t, err)
require.NotNil(t, doc.Paths)
err = doc.Validate(sl.Context)
require.NoError(t, err)
require.NotNil(t, doc.Paths)
t.Run("Roundtrip invopop/yaml", func(t *testing.T) {
justPaths, err := invopopYaml.Marshal(doc.Paths)
require.NoError(t, err)
require.NotNil(t, doc.Paths)
require.YAMLEq(t, `
/simple:
get:
summary: A simple GET request
responses:
'200':
description: OK
`[1:], string(justPaths))
marshalledYaml, err := invopopYaml.Marshal(doc)
require.NoError(t, err)
require.NotNil(t, doc.Paths)
require.YAMLEq(t, spec, string(marshalledYaml))
var newDoc openapi3.T
err = invopopYaml.Unmarshal(marshalledYaml, &newDoc)
require.NoError(t, err)
require.NotNil(t, newDoc.Paths)
require.Equal(t, doc, &newDoc)
})
t.Run("Roundtrip yaml.v3", func(t *testing.T) {
justPaths, err := doc.Paths.MarshalJSON()
require.NoError(t, err)
require.NotNil(t, doc.Paths)
require.YAMLEq(t, `
/simple:
get:
summary: A simple GET request
responses:
'200':
description: OK
`[1:], string(justPaths))
justPaths, err = v3.Marshal(doc.Paths)
require.NoError(t, err)
require.NotNil(t, doc.Paths)
require.YAMLEq(t, `
/simple:
get:
summary: A simple GET request
responses:
'200':
description: OK
`[1:], string(justPaths))
marshalledYaml, err := v3.Marshal(doc)
require.NoError(t, err)
require.NotNil(t, doc.Paths)
require.YAMLEq(t, spec, string(marshalledYaml))
t.Skip("TODO: impl https://pkg.go.dev/gopkg.in/yaml.v3#Unmarshaler on maplike types")
var newDoc openapi3.T
err = v3.Unmarshal(marshalledYaml, &newDoc)
require.NoError(t, err)
require.NotNil(t, newDoc.Paths)
require.Equal(t, doc, &newDoc)
})
}
kin-openapi-0.124.0/openapi3/license.go 0000664 0000000 0000000 00000002774 14604223742 0017575 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"encoding/json"
"errors"
)
// License is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#license-object
type License struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Name string `json:"name" yaml:"name"` // Required
URL string `json:"url,omitempty" yaml:"url,omitempty"`
}
// MarshalJSON returns the JSON encoding of License.
func (license License) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{}, 2+len(license.Extensions))
for k, v := range license.Extensions {
m[k] = v
}
m["name"] = license.Name
if x := license.URL; x != "" {
m["url"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets License to a copy of data.
func (license *License) UnmarshalJSON(data []byte) error {
type LicenseBis License
var x LicenseBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "name")
delete(x.Extensions, "url")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*license = License(x)
return nil
}
// Validate returns an error if License does not comply with the OpenAPI spec.
func (license *License) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if license.Name == "" {
return errors.New("value of license name must be a non-empty string")
}
return validateExtensions(ctx, license.Extensions)
}
kin-openapi-0.124.0/openapi3/link.go 0000664 0000000 0000000 00000005054 14604223742 0017102 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"encoding/json"
"errors"
"fmt"
)
// Link is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#link-object
type Link struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
OperationRef string `json:"operationRef,omitempty" yaml:"operationRef,omitempty"`
OperationID string `json:"operationId,omitempty" yaml:"operationId,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Parameters map[string]interface{} `json:"parameters,omitempty" yaml:"parameters,omitempty"`
Server *Server `json:"server,omitempty" yaml:"server,omitempty"`
RequestBody interface{} `json:"requestBody,omitempty" yaml:"requestBody,omitempty"`
}
// MarshalJSON returns the JSON encoding of Link.
func (link Link) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{}, 6+len(link.Extensions))
for k, v := range link.Extensions {
m[k] = v
}
if x := link.OperationRef; x != "" {
m["operationRef"] = x
}
if x := link.OperationID; x != "" {
m["operationId"] = x
}
if x := link.Description; x != "" {
m["description"] = x
}
if x := link.Parameters; len(x) != 0 {
m["parameters"] = x
}
if x := link.Server; x != nil {
m["server"] = x
}
if x := link.RequestBody; x != nil {
m["requestBody"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets Link to a copy of data.
func (link *Link) UnmarshalJSON(data []byte) error {
type LinkBis Link
var x LinkBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "operationRef")
delete(x.Extensions, "operationId")
delete(x.Extensions, "description")
delete(x.Extensions, "parameters")
delete(x.Extensions, "server")
delete(x.Extensions, "requestBody")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*link = Link(x)
return nil
}
// Validate returns an error if Link does not comply with the OpenAPI spec.
func (link *Link) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if link.OperationID == "" && link.OperationRef == "" {
return errors.New("missing operationId or operationRef on link")
}
if link.OperationID != "" && link.OperationRef != "" {
return fmt.Errorf("operationId %q and operationRef %q are mutually exclusive", link.OperationID, link.OperationRef)
}
return validateExtensions(ctx, link.Extensions)
}
kin-openapi-0.124.0/openapi3/load_cicular_ref_with_external_file_test.go 0000664 0000000 0000000 00000003345 14604223742 0026416 0 ustar 00root root 0000000 0000000 //go:build go1.16
// +build go1.16
package openapi3_test
import (
"embed"
"encoding/json"
"net/url"
"testing"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
)
//go:embed testdata/circularRef/*
var circularResSpecs embed.FS
func TestLoadCircularRefFromFile(t *testing.T) {
loader := openapi3.NewLoader()
loader.IsExternalRefsAllowed = true
loader.ReadFromURIFunc = func(loader *openapi3.Loader, uri *url.URL) ([]byte, error) {
return circularResSpecs.ReadFile(uri.Path)
}
got, err := loader.LoadFromFile("testdata/circularRef/base.yml")
require.NoError(t, err)
foo := &openapi3.SchemaRef{
Value: &openapi3.Schema{
Properties: map[string]*openapi3.SchemaRef{
"foo2": {
Ref: "other.yml#/components/schemas/Foo2", // reference to an external file
Value: &openapi3.Schema{
Properties: map[string]*openapi3.SchemaRef{
"id": {
Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}},
},
},
},
},
},
}
bar := &openapi3.SchemaRef{Value: &openapi3.Schema{Properties: make(map[string]*openapi3.SchemaRef)}}
// circular reference
bar.Value.Properties["foo"] = &openapi3.SchemaRef{Ref: "#/components/schemas/Foo", Value: foo.Value}
foo.Value.Properties["bar"] = &openapi3.SchemaRef{Ref: "#/components/schemas/Bar", Value: bar.Value}
want := &openapi3.T{
OpenAPI: "3.0.3",
Info: &openapi3.Info{
Title: "Recursive cyclic refs example",
Version: "1.0",
},
Components: &openapi3.Components{
Schemas: openapi3.Schemas{
"Foo": foo,
"Bar": bar,
},
},
}
jsoner := func(doc *openapi3.T) string {
data, err := json.Marshal(doc)
require.NoError(t, err)
return string(data)
}
require.JSONEq(t, jsoner(want), jsoner(got))
}
kin-openapi-0.124.0/openapi3/load_with_go_embed_test.go 0000664 0000000 0000000 00000001461 14604223742 0022775 0 ustar 00root root 0000000 0000000 //go:build go1.16
// +build go1.16
package openapi3_test
import (
"embed"
"fmt"
"net/url"
"github.com/getkin/kin-openapi/openapi3"
)
//go:embed testdata/recursiveRef/*
var fs embed.FS
func Example() {
loader := openapi3.NewLoader()
loader.IsExternalRefsAllowed = true
loader.ReadFromURIFunc = func(loader *openapi3.Loader, uri *url.URL) ([]byte, error) {
return fs.ReadFile(uri.Path)
}
doc, err := loader.LoadFromFile("testdata/recursiveRef/openapi.yml")
if err != nil {
panic(err)
}
if err = doc.Validate(loader.Context); err != nil {
panic(err)
}
fmt.Println(doc.
Paths.Value("/foo").
Get.Responses.Value("200").Value.
Content["application/json"].
Schema.Value.
Properties["foo2"].Value.
Properties["foo"].Value.
Properties["bar"].Value.
Type,
)
// Output: &[string]
}
kin-openapi-0.124.0/openapi3/loader.go 0000664 0000000 0000000 00000072430 14604223742 0017415 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/url"
"os"
"path"
"path/filepath"
"reflect"
"sort"
"strconv"
"strings"
)
var CircularReferenceError = "kin-openapi bug found: circular schema reference not handled"
var CircularReferenceCounter = 3
func foundUnresolvedRef(ref string) error {
return fmt.Errorf("found unresolved ref: %q", ref)
}
func failedToResolveRefFragmentPart(value, what string) error {
return fmt.Errorf("failed to resolve %q in fragment in URI: %q", what, value)
}
// Loader helps deserialize an OpenAPIv3 document
type Loader struct {
// IsExternalRefsAllowed enables visiting other files
IsExternalRefsAllowed bool
// ReadFromURIFunc allows overriding the any file/URL reading func
ReadFromURIFunc ReadFromURIFunc
Context context.Context
rootDir string
rootLocation string
visitedPathItemRefs map[string]struct{}
visitedDocuments map[string]*T
visitedCallback map[*Callback]struct{}
visitedExample map[*Example]struct{}
visitedHeader map[*Header]struct{}
visitedLink map[*Link]struct{}
visitedParameter map[*Parameter]struct{}
visitedRequestBody map[*RequestBody]struct{}
visitedResponse map[*Response]struct{}
visitedSchema map[*Schema]struct{}
visitedSecurityScheme map[*SecurityScheme]struct{}
}
// NewLoader returns an empty Loader
func NewLoader() *Loader {
return &Loader{
Context: context.Background(),
}
}
func (loader *Loader) resetVisitedPathItemRefs() {
loader.visitedPathItemRefs = make(map[string]struct{})
}
// LoadFromURI loads a spec from a remote URL
func (loader *Loader) LoadFromURI(location *url.URL) (*T, error) {
loader.resetVisitedPathItemRefs()
return loader.loadFromURIInternal(location)
}
// LoadFromFile loads a spec from a local file path
func (loader *Loader) LoadFromFile(location string) (*T, error) {
loader.rootDir = path.Dir(location)
return loader.LoadFromURI(&url.URL{Path: filepath.ToSlash(location)})
}
func (loader *Loader) loadFromURIInternal(location *url.URL) (*T, error) {
data, err := loader.readURL(location)
if err != nil {
return nil, err
}
return loader.loadFromDataWithPathInternal(data, location)
}
func (loader *Loader) allowsExternalRefs(ref string) (err error) {
if !loader.IsExternalRefsAllowed {
err = fmt.Errorf("encountered disallowed external reference: %q", ref)
}
return
}
func (loader *Loader) loadSingleElementFromURI(ref string, rootPath *url.URL, element interface{}) (*url.URL, error) {
if err := loader.allowsExternalRefs(ref); err != nil {
return nil, err
}
resolvedPath, err := resolvePathWithRef(ref, rootPath)
if err != nil {
return nil, err
}
if frag := resolvedPath.Fragment; frag != "" {
return nil, fmt.Errorf("unexpected ref fragment %q", frag)
}
data, err := loader.readURL(resolvedPath)
if err != nil {
return nil, err
}
if err := unmarshal(data, element); err != nil {
return nil, err
}
return resolvedPath, nil
}
func (loader *Loader) readURL(location *url.URL) ([]byte, error) {
if f := loader.ReadFromURIFunc; f != nil {
return f(loader, location)
}
return DefaultReadFromURI(loader, location)
}
// LoadFromStdin loads a spec from stdin
func (loader *Loader) LoadFromStdin() (*T, error) {
return loader.LoadFromIoReader(os.Stdin)
}
// LoadFromStdin loads a spec from io.Reader
func (loader *Loader) LoadFromIoReader(reader io.Reader) (*T, error) {
if reader == nil {
return nil, fmt.Errorf("invalid reader: %v", reader)
}
data, err := io.ReadAll(reader)
if err != nil {
return nil, err
}
return loader.LoadFromData(data)
}
// LoadFromData loads a spec from a byte array
func (loader *Loader) LoadFromData(data []byte) (*T, error) {
loader.resetVisitedPathItemRefs()
doc := &T{}
if err := unmarshal(data, doc); err != nil {
return nil, err
}
if err := loader.ResolveRefsIn(doc, nil); err != nil {
return nil, err
}
return doc, nil
}
// LoadFromDataWithPath takes the OpenAPI document data in bytes and a path where the resolver can find referred
// elements and returns a *T with all resolved data or an error if unable to load data or resolve refs.
func (loader *Loader) LoadFromDataWithPath(data []byte, location *url.URL) (*T, error) {
loader.resetVisitedPathItemRefs()
return loader.loadFromDataWithPathInternal(data, location)
}
func (loader *Loader) loadFromDataWithPathInternal(data []byte, location *url.URL) (*T, error) {
if loader.visitedDocuments == nil {
loader.visitedDocuments = make(map[string]*T)
loader.rootLocation = location.Path
}
uri := location.String()
if doc, ok := loader.visitedDocuments[uri]; ok {
return doc, nil
}
doc := &T{}
loader.visitedDocuments[uri] = doc
if err := unmarshal(data, doc); err != nil {
return nil, err
}
if err := loader.ResolveRefsIn(doc, location); err != nil {
return nil, err
}
return doc, nil
}
// ResolveRefsIn expands references if for instance spec was just unmarshaled
func (loader *Loader) ResolveRefsIn(doc *T, location *url.URL) (err error) {
if loader.Context == nil {
loader.Context = context.Background()
}
if loader.visitedPathItemRefs == nil {
loader.resetVisitedPathItemRefs()
}
if components := doc.Components; components != nil {
for _, component := range components.Headers {
if err = loader.resolveHeaderRef(doc, component, location); err != nil {
return
}
}
for _, component := range components.Parameters {
if err = loader.resolveParameterRef(doc, component, location); err != nil {
return
}
}
for _, component := range components.RequestBodies {
if err = loader.resolveRequestBodyRef(doc, component, location); err != nil {
return
}
}
for _, component := range components.Responses {
if err = loader.resolveResponseRef(doc, component, location); err != nil {
return
}
}
for _, component := range components.Schemas {
if err = loader.resolveSchemaRef(doc, component, location, []string{}); err != nil {
return
}
}
for _, component := range components.SecuritySchemes {
if err = loader.resolveSecuritySchemeRef(doc, component, location); err != nil {
return
}
}
examples := make([]string, 0, len(components.Examples))
for name := range components.Examples {
examples = append(examples, name)
}
sort.Strings(examples)
for _, name := range examples {
component := components.Examples[name]
if err = loader.resolveExampleRef(doc, component, location); err != nil {
return
}
}
for _, component := range components.Callbacks {
if err = loader.resolveCallbackRef(doc, component, location); err != nil {
return
}
}
}
// Visit all operations
for _, pathItem := range doc.Paths.Map() {
if pathItem == nil {
continue
}
if err = loader.resolvePathItemRef(doc, pathItem, location); err != nil {
return
}
}
return
}
func join(basePath *url.URL, relativePath *url.URL) *url.URL {
if basePath == nil {
return relativePath
}
newPath := *basePath
newPath.Path = path.Join(path.Dir(newPath.Path), relativePath.Path)
return &newPath
}
func resolvePath(basePath *url.URL, componentPath *url.URL) *url.URL {
if is_file(componentPath) {
// support absolute paths
if componentPath.Path[0] == '/' {
return componentPath
}
return join(basePath, componentPath)
}
return componentPath
}
func resolvePathWithRef(ref string, rootPath *url.URL) (*url.URL, error) {
parsedURL, err := url.Parse(ref)
if err != nil {
return nil, fmt.Errorf("cannot parse reference: %q: %w", ref, err)
}
resolvedPath := resolvePath(rootPath, parsedURL)
resolvedPath.Fragment = parsedURL.Fragment
return resolvedPath, nil
}
func isSingleRefElement(ref string) bool {
return !strings.Contains(ref, "#")
}
func (loader *Loader) resolveComponent(doc *T, ref string, path *url.URL, resolved interface{}) (
componentDoc *T,
componentPath *url.URL,
err error,
) {
if componentDoc, ref, componentPath, err = loader.resolveRef(doc, ref, path); err != nil {
return nil, nil, err
}
parsedURL, err := url.Parse(ref)
if err != nil {
return nil, nil, fmt.Errorf("cannot parse reference: %q: %v", ref, parsedURL)
}
fragment := parsedURL.Fragment
if fragment == "" {
fragment = "/"
}
if fragment[0] != '/' {
return nil, nil, fmt.Errorf("expected fragment prefix '#/' in URI %q", ref)
}
drill := func(cursor interface{}) (interface{}, error) {
for _, pathPart := range strings.Split(fragment[1:], "/") {
pathPart = unescapeRefString(pathPart)
attempted := false
switch c := cursor.(type) {
// Special case of T
// See issue856: a ref to doc => we assume that doc is a T => things live in T.Extensions
case *T:
if pathPart == "" {
cursor = c.Extensions
attempted = true
}
// Special case due to multijson
case *SchemaRef:
if pathPart == "additionalProperties" {
if ap := c.Value.AdditionalProperties.Has; ap != nil {
cursor = *ap
} else {
cursor = c.Value.AdditionalProperties.Schema
}
attempted = true
}
case *Responses:
cursor = c.m // m map[string]*ResponseRef
case *Callback:
cursor = c.m // m map[string]*PathItem
case *Paths:
cursor = c.m // m map[string]*PathItem
}
if !attempted {
if cursor, err = drillIntoField(cursor, pathPart); err != nil {
e := failedToResolveRefFragmentPart(ref, pathPart)
return nil, fmt.Errorf("%s: %w", e, err)
}
}
if cursor == nil {
return nil, failedToResolveRefFragmentPart(ref, pathPart)
}
}
return cursor, nil
}
var cursor interface{}
if cursor, err = drill(componentDoc); err != nil {
if path == nil {
return nil, nil, err
}
var err2 error
data, err2 := loader.readURL(path)
if err2 != nil {
return nil, nil, err
}
if err2 = unmarshal(data, &cursor); err2 != nil {
return nil, nil, err
}
if cursor, err2 = drill(cursor); err2 != nil || cursor == nil {
return nil, nil, err
}
err = nil
}
switch {
case reflect.TypeOf(cursor) == reflect.TypeOf(resolved):
reflect.ValueOf(resolved).Elem().Set(reflect.ValueOf(cursor).Elem())
return componentDoc, componentPath, nil
case reflect.TypeOf(cursor) == reflect.TypeOf(map[string]interface{}{}):
codec := func(got, expect interface{}) error {
enc, err := json.Marshal(got)
if err != nil {
return err
}
if err = json.Unmarshal(enc, expect); err != nil {
return err
}
return nil
}
if err := codec(cursor, resolved); err != nil {
return nil, nil, fmt.Errorf("bad data in %q (expecting %s)", ref, readableType(resolved))
}
return componentDoc, componentPath, nil
default:
return nil, nil, fmt.Errorf("bad data in %q (expecting %s)", ref, readableType(resolved))
}
}
func readableType(x interface{}) string {
switch x.(type) {
case *Callback:
return "callback object"
case *CallbackRef:
return "ref to callback object"
case *ExampleRef:
return "ref to example object"
case *HeaderRef:
return "ref to header object"
case *LinkRef:
return "ref to link object"
case *ParameterRef:
return "ref to parameter object"
case *PathItem:
return "pathItem object"
case *RequestBodyRef:
return "ref to requestBody object"
case *ResponseRef:
return "ref to response object"
case *SchemaRef:
return "ref to schema object"
case *SecuritySchemeRef:
return "ref to securityScheme object"
default:
panic(fmt.Sprintf("unreachable %T", x))
}
}
func drillIntoField(cursor interface{}, fieldName string) (interface{}, error) {
switch val := reflect.Indirect(reflect.ValueOf(cursor)); val.Kind() {
case reflect.Map:
elementValue := val.MapIndex(reflect.ValueOf(fieldName))
if !elementValue.IsValid() {
return nil, fmt.Errorf("map key %q not found", fieldName)
}
return elementValue.Interface(), nil
case reflect.Slice:
i, err := strconv.ParseUint(fieldName, 10, 32)
if err != nil {
return nil, err
}
index := int(i)
if 0 > index || index >= val.Len() {
return nil, errors.New("slice index out of bounds")
}
return val.Index(index).Interface(), nil
case reflect.Struct:
hasFields := false
for i := 0; i < val.NumField(); i++ {
hasFields = true
if yamlTag := val.Type().Field(i).Tag.Get("yaml"); yamlTag != "-" {
if tagName := strings.Split(yamlTag, ",")[0]; tagName != "" {
if fieldName == tagName {
return val.Field(i).Interface(), nil
}
}
}
}
// if cursor is a "ref wrapper" struct (e.g. RequestBodyRef),
if _, ok := val.Type().FieldByName("Value"); ok {
// try digging into its Value field
return drillIntoField(val.FieldByName("Value").Interface(), fieldName)
}
if hasFields {
if ff := val.Type().Field(0); ff.PkgPath == "" && ff.Name == "Extensions" {
extensions := val.Field(0).Interface().(map[string]interface{})
if enc, ok := extensions[fieldName]; ok {
return enc, nil
}
}
}
return nil, fmt.Errorf("struct field %q not found", fieldName)
default:
return nil, errors.New("not a map, slice nor struct")
}
}
func (loader *Loader) resolveRef(doc *T, ref string, path *url.URL) (*T, string, *url.URL, error) {
if ref != "" && ref[0] == '#' {
return doc, ref, path, nil
}
if err := loader.allowsExternalRefs(ref); err != nil {
return nil, "", nil, err
}
resolvedPath, err := resolvePathWithRef(ref, path)
if err != nil {
return nil, "", nil, err
}
fragment := "#" + resolvedPath.Fragment
resolvedPath.Fragment = ""
if doc, err = loader.loadFromURIInternal(resolvedPath); err != nil {
return nil, "", nil, fmt.Errorf("error resolving reference %q: %w", ref, err)
}
return doc, fragment, resolvedPath, nil
}
var (
errMUSTCallback = errors.New("invalid callback: value MUST be an object")
errMUSTExample = errors.New("invalid example: value MUST be an object")
errMUSTHeader = errors.New("invalid header: value MUST be an object")
errMUSTLink = errors.New("invalid link: value MUST be an object")
errMUSTParameter = errors.New("invalid parameter: value MUST be an object")
errMUSTPathItem = errors.New("invalid path item: value MUST be an object")
errMUSTRequestBody = errors.New("invalid requestBody: value MUST be an object")
errMUSTResponse = errors.New("invalid response: value MUST be an object")
errMUSTSchema = errors.New("invalid schema: value MUST be an object")
errMUSTSecurityScheme = errors.New("invalid securityScheme: value MUST be an object")
)
func (loader *Loader) resolveHeaderRef(doc *T, component *HeaderRef, documentPath *url.URL) (err error) {
if component.isEmpty() {
return errMUSTHeader
}
if component.Value != nil {
if loader.visitedHeader == nil {
loader.visitedHeader = make(map[*Header]struct{})
}
if _, ok := loader.visitedHeader[component.Value]; ok {
return nil
}
loader.visitedHeader[component.Value] = struct{}{}
}
if ref := component.Ref; ref != "" {
if isSingleRefElement(ref) {
var header Header
if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, &header); err != nil {
return err
}
component.Value = &header
} else {
var resolved HeaderRef
doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
if err != nil {
return err
}
if err := loader.resolveHeaderRef(doc, &resolved, componentPath); err != nil {
if err == errMUSTHeader {
return nil
}
return err
}
component.Value = resolved.Value
}
}
value := component.Value
if value == nil {
return nil
}
if schema := value.Schema; schema != nil {
if err := loader.resolveSchemaRef(doc, schema, documentPath, []string{}); err != nil {
return err
}
}
return nil
}
func (loader *Loader) resolveParameterRef(doc *T, component *ParameterRef, documentPath *url.URL) (err error) {
if component.isEmpty() {
return errMUSTParameter
}
if component.Value != nil {
if loader.visitedParameter == nil {
loader.visitedParameter = make(map[*Parameter]struct{})
}
if _, ok := loader.visitedParameter[component.Value]; ok {
return nil
}
loader.visitedParameter[component.Value] = struct{}{}
}
if ref := component.Ref; ref != "" {
if isSingleRefElement(ref) {
var param Parameter
if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, ¶m); err != nil {
return err
}
component.Value = ¶m
} else {
var resolved ParameterRef
doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
if err != nil {
return err
}
if err := loader.resolveParameterRef(doc, &resolved, componentPath); err != nil {
if err == errMUSTParameter {
return nil
}
return err
}
component.Value = resolved.Value
}
}
value := component.Value
if value == nil {
return nil
}
if value.Content != nil && value.Schema != nil {
return errors.New("cannot contain both schema and content in a parameter")
}
for _, contentType := range value.Content {
if schema := contentType.Schema; schema != nil {
if err := loader.resolveSchemaRef(doc, schema, documentPath, []string{}); err != nil {
return err
}
}
}
if schema := value.Schema; schema != nil {
if err := loader.resolveSchemaRef(doc, schema, documentPath, []string{}); err != nil {
return err
}
}
return nil
}
func (loader *Loader) resolveRequestBodyRef(doc *T, component *RequestBodyRef, documentPath *url.URL) (err error) {
if component.isEmpty() {
return errMUSTRequestBody
}
if component.Value != nil {
if loader.visitedRequestBody == nil {
loader.visitedRequestBody = make(map[*RequestBody]struct{})
}
if _, ok := loader.visitedRequestBody[component.Value]; ok {
return nil
}
loader.visitedRequestBody[component.Value] = struct{}{}
}
if ref := component.Ref; ref != "" {
if isSingleRefElement(ref) {
var requestBody RequestBody
if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, &requestBody); err != nil {
return err
}
component.Value = &requestBody
} else {
var resolved RequestBodyRef
doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
if err != nil {
return err
}
if err = loader.resolveRequestBodyRef(doc, &resolved, componentPath); err != nil {
if err == errMUSTRequestBody {
return nil
}
return err
}
component.Value = resolved.Value
}
}
value := component.Value
if value == nil {
return nil
}
for _, contentType := range value.Content {
if contentType == nil {
continue
}
examples := make([]string, 0, len(contentType.Examples))
for name := range contentType.Examples {
examples = append(examples, name)
}
sort.Strings(examples)
for _, name := range examples {
example := contentType.Examples[name]
if err := loader.resolveExampleRef(doc, example, documentPath); err != nil {
return err
}
contentType.Examples[name] = example
}
if schema := contentType.Schema; schema != nil {
if err := loader.resolveSchemaRef(doc, schema, documentPath, []string{}); err != nil {
return err
}
}
}
return nil
}
func (loader *Loader) resolveResponseRef(doc *T, component *ResponseRef, documentPath *url.URL) (err error) {
if component.isEmpty() {
return errMUSTResponse
}
if component.Value != nil {
if loader.visitedResponse == nil {
loader.visitedResponse = make(map[*Response]struct{})
}
if _, ok := loader.visitedResponse[component.Value]; ok {
return nil
}
loader.visitedResponse[component.Value] = struct{}{}
}
if ref := component.Ref; ref != "" {
if isSingleRefElement(ref) {
var resp Response
if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, &resp); err != nil {
return err
}
component.Value = &resp
} else {
var resolved ResponseRef
doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
if err != nil {
return err
}
if err := loader.resolveResponseRef(doc, &resolved, componentPath); err != nil {
if err == errMUSTResponse {
return nil
}
return err
}
component.Value = resolved.Value
}
}
value := component.Value
if value == nil {
return nil
}
for _, header := range value.Headers {
if err := loader.resolveHeaderRef(doc, header, documentPath); err != nil {
return err
}
}
for _, contentType := range value.Content {
if contentType == nil {
continue
}
examples := make([]string, 0, len(contentType.Examples))
for name := range contentType.Examples {
examples = append(examples, name)
}
sort.Strings(examples)
for _, name := range examples {
example := contentType.Examples[name]
if err := loader.resolveExampleRef(doc, example, documentPath); err != nil {
return err
}
contentType.Examples[name] = example
}
if schema := contentType.Schema; schema != nil {
if err := loader.resolveSchemaRef(doc, schema, documentPath, []string{}); err != nil {
return err
}
contentType.Schema = schema
}
}
for _, link := range value.Links {
if err := loader.resolveLinkRef(doc, link, documentPath); err != nil {
return err
}
}
return nil
}
func (loader *Loader) resolveSchemaRef(doc *T, component *SchemaRef, documentPath *url.URL, visited []string) (err error) {
if component.isEmpty() {
return errMUSTSchema
}
if component.Value != nil {
if loader.visitedSchema == nil {
loader.visitedSchema = make(map[*Schema]struct{})
}
if _, ok := loader.visitedSchema[component.Value]; ok {
return nil
}
loader.visitedSchema[component.Value] = struct{}{}
}
if ref := component.Ref; ref != "" {
if isSingleRefElement(ref) {
var schema Schema
if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, &schema); err != nil {
return err
}
component.Value = &schema
} else {
if visitedLimit(visited, ref) {
visited = append(visited, ref)
return fmt.Errorf("%s with length %d - %s", CircularReferenceError, len(visited), strings.Join(visited, " -> "))
}
visited = append(visited, ref)
var resolved SchemaRef
doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
if err != nil {
return err
}
if err := loader.resolveSchemaRef(doc, &resolved, componentPath, visited); err != nil {
if err == errMUSTSchema {
return nil
}
return err
}
component.Value = resolved.Value
}
if loader.visitedSchema == nil {
loader.visitedSchema = make(map[*Schema]struct{})
}
loader.visitedSchema[component.Value] = struct{}{}
}
value := component.Value
if value == nil {
return nil
}
// ResolveRefs referred schemas
if v := value.Items; v != nil {
if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil {
return err
}
}
for _, v := range value.Properties {
if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil {
return err
}
}
if v := value.AdditionalProperties.Schema; v != nil {
if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil {
return err
}
}
if v := value.Not; v != nil {
if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil {
return err
}
}
for _, v := range value.AllOf {
if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil {
return err
}
}
for _, v := range value.AnyOf {
if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil {
return err
}
}
for _, v := range value.OneOf {
if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil {
return err
}
}
return nil
}
func (loader *Loader) resolveSecuritySchemeRef(doc *T, component *SecuritySchemeRef, documentPath *url.URL) (err error) {
if component.isEmpty() {
return errMUSTSecurityScheme
}
if component.Value != nil {
if loader.visitedSecurityScheme == nil {
loader.visitedSecurityScheme = make(map[*SecurityScheme]struct{})
}
if _, ok := loader.visitedSecurityScheme[component.Value]; ok {
return nil
}
loader.visitedSecurityScheme[component.Value] = struct{}{}
}
if ref := component.Ref; ref != "" {
if isSingleRefElement(ref) {
var scheme SecurityScheme
if _, err = loader.loadSingleElementFromURI(ref, documentPath, &scheme); err != nil {
return err
}
component.Value = &scheme
} else {
var resolved SecuritySchemeRef
doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
if err != nil {
return err
}
if err := loader.resolveSecuritySchemeRef(doc, &resolved, componentPath); err != nil {
if err == errMUSTSecurityScheme {
return nil
}
return err
}
component.Value = resolved.Value
}
}
return nil
}
func (loader *Loader) resolveExampleRef(doc *T, component *ExampleRef, documentPath *url.URL) (err error) {
if component.isEmpty() {
return errMUSTExample
}
if component.Value != nil {
if loader.visitedExample == nil {
loader.visitedExample = make(map[*Example]struct{})
}
if _, ok := loader.visitedExample[component.Value]; ok {
return nil
}
loader.visitedExample[component.Value] = struct{}{}
}
if ref := component.Ref; ref != "" {
if isSingleRefElement(ref) {
var example Example
if _, err = loader.loadSingleElementFromURI(ref, documentPath, &example); err != nil {
return err
}
component.Value = &example
} else {
var resolved ExampleRef
doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
if err != nil {
return err
}
if err := loader.resolveExampleRef(doc, &resolved, componentPath); err != nil {
if err == errMUSTExample {
return nil
}
return err
}
component.Value = resolved.Value
}
}
return nil
}
func (loader *Loader) resolveCallbackRef(doc *T, component *CallbackRef, documentPath *url.URL) (err error) {
if component.isEmpty() {
return errMUSTCallback
}
if component.Value != nil {
if loader.visitedCallback == nil {
loader.visitedCallback = make(map[*Callback]struct{})
}
if _, ok := loader.visitedCallback[component.Value]; ok {
return nil
}
loader.visitedCallback[component.Value] = struct{}{}
}
if ref := component.Ref; ref != "" {
if isSingleRefElement(ref) {
var resolved Callback
if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, &resolved); err != nil {
return err
}
component.Value = &resolved
} else {
var resolved CallbackRef
doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
if err != nil {
return err
}
if err = loader.resolveCallbackRef(doc, &resolved, componentPath); err != nil {
if err == errMUSTCallback {
return nil
}
return err
}
component.Value = resolved.Value
}
}
value := component.Value
if value == nil {
return nil
}
for _, pathItem := range value.Map() {
if err = loader.resolvePathItemRef(doc, pathItem, documentPath); err != nil {
return err
}
}
return nil
}
func (loader *Loader) resolveLinkRef(doc *T, component *LinkRef, documentPath *url.URL) (err error) {
if component.isEmpty() {
return errMUSTLink
}
if component.Value != nil {
if loader.visitedLink == nil {
loader.visitedLink = make(map[*Link]struct{})
}
if _, ok := loader.visitedLink[component.Value]; ok {
return nil
}
loader.visitedLink[component.Value] = struct{}{}
}
if ref := component.Ref; ref != "" {
if isSingleRefElement(ref) {
var link Link
if _, err = loader.loadSingleElementFromURI(ref, documentPath, &link); err != nil {
return err
}
component.Value = &link
} else {
var resolved LinkRef
doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
if err != nil {
return err
}
if err := loader.resolveLinkRef(doc, &resolved, componentPath); err != nil {
if err == errMUSTLink {
return nil
}
return err
}
component.Value = resolved.Value
}
}
return nil
}
func (loader *Loader) resolvePathItemRef(doc *T, pathItem *PathItem, documentPath *url.URL) (err error) {
if pathItem == nil {
err = errMUSTPathItem
return
}
if ref := pathItem.Ref; ref != "" {
if !pathItem.isEmpty() {
return
}
if isSingleRefElement(ref) {
var p PathItem
if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, &p); err != nil {
return
}
*pathItem = p
} else {
var resolved PathItem
if doc, documentPath, err = loader.resolveComponent(doc, ref, documentPath, &resolved); err != nil {
if err == errMUSTPathItem {
return nil
}
return
}
*pathItem = resolved
}
pathItem.Ref = ref
}
for _, parameter := range pathItem.Parameters {
if err = loader.resolveParameterRef(doc, parameter, documentPath); err != nil {
return
}
}
for _, operation := range pathItem.Operations() {
for _, parameter := range operation.Parameters {
if err = loader.resolveParameterRef(doc, parameter, documentPath); err != nil {
return
}
}
if requestBody := operation.RequestBody; requestBody != nil {
if err = loader.resolveRequestBodyRef(doc, requestBody, documentPath); err != nil {
return
}
}
for _, response := range operation.Responses.Map() {
if err = loader.resolveResponseRef(doc, response, documentPath); err != nil {
return
}
}
for _, callback := range operation.Callbacks {
if err = loader.resolveCallbackRef(doc, callback, documentPath); err != nil {
return
}
}
}
return
}
func unescapeRefString(ref string) string {
return strings.Replace(strings.Replace(ref, "~1", "/", -1), "~0", "~", -1)
}
func visitedLimit(visited []string, ref string) bool {
visitedCount := 0
for _, v := range visited {
if v == ref {
visitedCount++
if visitedCount >= CircularReferenceCounter {
return true
}
}
}
return false
}
kin-openapi-0.124.0/openapi3/loader_empty_response_description_test.go 0000664 0000000 0000000 00000003666 14604223742 0026220 0 ustar 00root root 0000000 0000000 package openapi3
import (
"encoding/json"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
func TestJSONSpecResponseDescriptionEmptiness(t *testing.T) {
const spec = `
{
"info": {
"description": "A sample API to illustrate OpenAPI concepts",
"title": "Sample API",
"version": "1.0.0"
},
"openapi": "3.0.0",
"paths": {
"/path1": {
"get": {
"responses": {
"200": {
"description": ""
}
}
}
}
}
}
`
{
spec := []byte(spec)
loader := NewLoader()
doc, err := loader.LoadFromData(spec)
require.NoError(t, err)
require.Equal(t, "", *doc.Paths.Value("/path1").Get.Responses.Value("200").Value.Description)
t.Log("Empty description provided: valid spec")
err = doc.Validate(loader.Context)
require.NoError(t, err)
}
{
spec := []byte(strings.Replace(spec, `"description": ""`, `"description": "My response"`, 1))
loader := NewLoader()
doc, err := loader.LoadFromData(spec)
require.NoError(t, err)
require.Equal(t, "My response", *doc.Paths.Value("/path1").Get.Responses.Value("200").Value.Description)
t.Log("Non-empty description provided: valid spec")
err = doc.Validate(loader.Context)
require.NoError(t, err)
}
noDescriptionIsInvalid := func(data []byte) *T {
loader := NewLoader()
doc, err := loader.LoadFromData(data)
require.NoError(t, err)
require.Nil(t, doc.Paths.Value("/path1").Get.Responses.Value("200").Value.Description)
t.Log("No description provided: invalid spec")
err = doc.Validate(loader.Context)
require.Error(t, err)
return doc
}
var docWithNoResponseDescription *T
{
spec := []byte(strings.Replace(spec, `"description": ""`, ``, 1))
docWithNoResponseDescription = noDescriptionIsInvalid(spec)
}
str, err := json.Marshal(docWithNoResponseDescription)
require.NoError(t, err)
require.NotEmpty(t, str)
t.Log("Reserialization does not set description")
_ = noDescriptionIsInvalid(str)
}
kin-openapi-0.124.0/openapi3/loader_http_error_test.go 0000664 0000000 0000000 00000005610 14604223742 0022720 0 ustar 00root root 0000000 0000000 package openapi3
import (
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/stretchr/testify/require"
)
func TestLoadReferenceFromRemoteURLFailsWithHttpError(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
fmt.Fprint(w, "")
}))
defer ts.Close()
spec := []byte(`
{
"openapi": "3.0.0",
"info": {
"title": "",
"version": "1"
},
"paths": {
"/test": {
"post": {
"responses": {
"default": {
"description": "test",
"headers": {
"X-TEST-HEADER": {
"$ref": "` + ts.URL + `/components.openapi.json#/components/headers/CustomTestHeader"
}
}
}
}
}
}
}
}`)
loader := NewLoader()
loader.IsExternalRefsAllowed = true
_, err := loader.LoadFromDataWithPath(spec, &url.URL{Path: "testdata/testfilename.openapi.json"})
require.EqualError(t, err, fmt.Sprintf(`error resolving reference "%s/components.openapi.json#/components/headers/CustomTestHeader": error loading "%s/components.openapi.json": request returned status code 400`, ts.URL, ts.URL))
_, err = loader.LoadFromData(spec)
require.EqualError(t, err, fmt.Sprintf(`error resolving reference "%s/components.openapi.json#/components/headers/CustomTestHeader": error loading "%s/components.openapi.json": request returned status code 400`, ts.URL, ts.URL))
}
func TestLoadFromRemoteURLFailsWithHttpError(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
fmt.Fprint(w, "")
}))
defer ts.Close()
spec := []byte(`
{
"openapi": "3.0.0",
"info": {
"title": "",
"version": "1"
},
"paths": {
"/test": {
"post": {
"responses": {
"default": {
"description": "test",
"headers": {
"X-TEST-HEADER": {
"$ref": "` + ts.URL + `/components.openapi.json"
}
}
}
}
}
}
}
}`)
loader := NewLoader()
loader.IsExternalRefsAllowed = true
doc, err := loader.LoadFromDataWithPath(spec, &url.URL{Path: "testdata/testfilename.openapi.json"})
require.Nil(t, doc)
require.EqualError(t, err, fmt.Sprintf(`error loading "%s/components.openapi.json": request returned status code 400`, ts.URL))
doc, err = loader.LoadFromData(spec)
require.Nil(t, doc)
require.EqualError(t, err, fmt.Sprintf(`error loading "%s/components.openapi.json": request returned status code 400`, ts.URL))
}
kin-openapi-0.124.0/openapi3/loader_issue212_test.go 0000664 0000000 0000000 00000004320 14604223742 0022102 0 ustar 00root root 0000000 0000000 package openapi3
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
)
func TestIssue212(t *testing.T) {
spec := `
openapi: 3.0.1
info:
title: 'test'
version: 1.0.0
servers:
- url: /api
paths:
/available-products:
get:
operationId: getAvailableProductCollection
responses:
"200":
description: test
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/AvailableProduct"
components:
schemas:
AvailableProduct:
type: object
properties:
id:
type: string
type:
type: string
name:
type: string
media:
type: object
properties:
documents:
type: array
items:
allOf:
- $ref: "#/components/schemas/AvailableProduct/properties/previewImage/allOf/0"
- type: object
properties:
uri:
type: string
pattern: ^\/documents\/[0-9a-f]{64}$
previewImage:
allOf:
- type: object
required:
- id
- uri
properties:
id:
type: string
uri:
type: string
- type: object
properties:
uri:
type: string
pattern: ^\/images\/[0-9a-f]{64}$
`
loader := NewLoader()
doc, err := loader.LoadFromData([]byte(spec))
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
expected, err := json.Marshal(&Schema{
Type: &Types{"object"},
Required: []string{"id", "uri"},
Properties: Schemas{
"id": {Value: &Schema{Type: &Types{"string"}}},
"uri": {Value: &Schema{Type: &Types{"string"}}},
},
},
)
require.NoError(t, err)
got, err := json.Marshal(doc.Components.Schemas["AvailableProduct"].Value.Properties["media"].Value.Properties["documents"].Value.Items.Value.AllOf[0].Value)
require.NoError(t, err)
require.Equal(t, expected, got)
}
kin-openapi-0.124.0/openapi3/loader_issue220_test.go 0000664 0000000 0000000 00000001245 14604223742 0022104 0 ustar 00root root 0000000 0000000 package openapi3
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
)
func TestIssue220(t *testing.T) {
for _, specPath := range []string{
"testdata/my-openapi.json",
filepath.FromSlash("testdata/my-openapi.json"),
} {
t.Logf("specPath: %q", specPath)
loader := NewLoader()
loader.IsExternalRefsAllowed = true
doc, err := loader.LoadFromFile(specPath)
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
require.Equal(t, &Types{"integer"}, doc.
Paths.Value("/foo").
Get.Responses.Value("200").Value.
Content["application/json"].
Schema.Value.Properties["bar"].Value.
Type)
}
}
kin-openapi-0.124.0/openapi3/loader_issue235_test.go 0000664 0000000 0000000 00000001201 14604223742 0022102 0 ustar 00root root 0000000 0000000 package openapi3
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestIssue235OK(t *testing.T) {
loader := NewLoader()
loader.IsExternalRefsAllowed = true
doc, err := loader.LoadFromFile("testdata/issue235.spec0.yml")
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
}
func TestIssue235CircularDep(t *testing.T) {
t.Skip("TODO: return an error on circular dependencies between external files of a spec")
loader := NewLoader()
loader.IsExternalRefsAllowed = true
doc, err := loader.LoadFromFile("testdata/issue235.spec0-typo.yml")
require.Nil(t, doc)
require.Error(t, err)
}
kin-openapi-0.124.0/openapi3/loader_outside_refs_test.go 0000664 0000000 0000000 00000002423 14604223742 0023222 0 ustar 00root root 0000000 0000000 package openapi3
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestLoadOutsideRefs(t *testing.T) {
loader := NewLoader()
loader.IsExternalRefsAllowed = true
doc, err := loader.LoadFromFile("testdata/303bis/service.yaml")
require.NoError(t, err)
require.NotNil(t, doc)
err = doc.Validate(loader.Context)
require.NoError(t, err)
require.Equal(t, &Types{"string"}, doc.
Paths.Value("/service").
Get.
Responses.Value("200").Value.
Content["application/json"].
Schema.Value.
Items.Value.
AllOf[0].Value.
Properties["created_at"].Value.
Type)
}
func TestIssue423(t *testing.T) {
spec := `
info:
description: test
title: test
version: 0.0.0
openapi: 3.0.1
paths:
/api/bundles:
get:
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/Data'
components:
schemas:
Data:
description: rbac Data
properties:
roles:
$ref: https://raw.githubusercontent.com/kubernetes/kubernetes/132f29769dfecfc808adc58f756be43171054094/api/openapi-spec/swagger.json#/definitions/io.k8s.api.rbac.v1.RoleList
`
loader := NewLoader()
loader.IsExternalRefsAllowed = true
loader.LoadFromData([]byte(spec))
}
kin-openapi-0.124.0/openapi3/loader_paths_test.go 0000664 0000000 0000000 00000001351 14604223742 0021645 0 ustar 00root root 0000000 0000000 package openapi3
import (
"strings"
"testing"
"github.com/stretchr/testify/require"
)
func TestPathsMustStartWithSlash(t *testing.T) {
spec := `
openapi: "3.0"
info:
version: "1.0"
title: sample
paths:
PATH:
get:
responses:
200:
description: description
`
for path, expectedErr := range map[string]string{
"foo/bar": "invalid paths: path \"foo/bar\" does not start with a forward slash (/)",
"/foo/bar": "",
} {
loader := NewLoader()
doc, err := loader.LoadFromData([]byte(strings.Replace(spec, "PATH", path, 1)))
require.NoError(t, err)
err = doc.Validate(loader.Context)
if expectedErr != "" {
require.EqualError(t, err, expectedErr)
} else {
require.NoError(t, err)
}
}
}
kin-openapi-0.124.0/openapi3/loader_read_from_uri_func_test.go 0000664 0000000 0000000 00000004757 14604223742 0024373 0 ustar 00root root 0000000 0000000 package openapi3
import (
"fmt"
"net/url"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
)
func TestLoaderReadFromURIFunc(t *testing.T) {
loader := NewLoader()
loader.IsExternalRefsAllowed = true
loader.ReadFromURIFunc = func(loader *Loader, url *url.URL) ([]byte, error) {
return os.ReadFile(filepath.Join("testdata", url.Path))
}
doc, err := loader.LoadFromFile("recursiveRef/openapi.yml")
require.NoError(t, err)
require.NotNil(t, doc)
require.NoError(t, doc.Validate(loader.Context))
require.Equal(t, "bar", doc.
Paths.Value("/foo").
Get.
Responses.Status(200).Value.
Content.Get("application/json").
Schema.Value.
Properties["foo2"].Value.
Properties["foo"].Value.
Properties["bar"].Value.
Example)
}
type multipleSourceLoaderExample struct {
Sources map[string][]byte
}
func (l *multipleSourceLoaderExample) LoadFromURI(
loader *Loader,
location *url.URL,
) ([]byte, error) {
source := l.resolveSourceFromURI(location)
if source == nil {
return nil, fmt.Errorf("unsupported URI: %q", location.String())
}
return source, nil
}
func (l *multipleSourceLoaderExample) resolveSourceFromURI(location fmt.Stringer) []byte {
return l.Sources[location.String()]
}
func TestResolveSchemaExternalRef(t *testing.T) {
rootLocation := &url.URL{Scheme: "http", Host: "example.com", Path: "spec.json"}
externalLocation := &url.URL{Scheme: "http", Host: "example.com", Path: "external.json"}
rootSpec := []byte(fmt.Sprintf(
`{"openapi":"3.0.0","info":{"title":"MyAPI","version":"0.1","description":"An API"},"paths":{},"components":{"schemas":{"Root":{"allOf":[{"$ref":"%s#/components/schemas/External"}]}}}}`,
externalLocation.String(),
))
externalSpec := []byte(`{"openapi":"3.0.0","info":{"title":"MyAPI","version":"0.1","description":"External Spec"},"paths":{},"components":{"schemas":{"External":{"type":"string"}}}}`)
multipleSourceLoader := &multipleSourceLoaderExample{
Sources: map[string][]byte{
rootLocation.String(): rootSpec,
externalLocation.String(): externalSpec,
},
}
loader := &Loader{
IsExternalRefsAllowed: true,
ReadFromURIFunc: multipleSourceLoader.LoadFromURI,
}
doc, err := loader.LoadFromURI(rootLocation)
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
refRootVisited := doc.Components.Schemas["Root"].Value.AllOf[0]
require.Equal(t, fmt.Sprintf("%s#/components/schemas/External", externalLocation.String()), refRootVisited.Ref)
require.NotNil(t, refRootVisited.Value)
}
kin-openapi-0.124.0/openapi3/loader_recursive_ref_test.go 0000664 0000000 0000000 00000002715 14604223742 0023376 0 ustar 00root root 0000000 0000000 package openapi3
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestLoaderSupportsRecursiveReference(t *testing.T) {
loader := NewLoader()
loader.IsExternalRefsAllowed = true
doc, err := loader.LoadFromFile("testdata/recursiveRef/openapi.yml")
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
require.Equal(t, "bar", doc.
Paths.Value("/foo").
Get.Responses.Status(200).Value.
Content.Get("application/json").
Schema.Value.Properties["foo2"].Value.Properties["foo"].Value.Properties["bar"].Value.Example)
require.Equal(t, "ErrorDetails", doc.
Paths.Value("/foo").
Get.Responses.Status(400).Value.
Content.Get("application/json").
Schema.Value.Title)
require.Equal(t, "ErrorDetails", doc.
Paths.Value("/double-ref-foo").
Get.Responses.Status(400).Value.
Content.Get("application/json").
Schema.Value.Title)
}
func TestIssue447(t *testing.T) {
loader := NewLoader()
doc, err := loader.LoadFromData([]byte(`
openapi: 3.0.1
info:
title: Recursive refs example
version: "1.0"
paths: {}
components:
schemas:
Complex:
type: object
properties:
parent:
$ref: '#/components/schemas/Complex'
`))
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
require.Equal(t, &Types{"object"}, doc.Components.
Schemas["Complex"].
Value.Properties["parent"].
Value.Properties["parent"].
Value.Properties["parent"].
Value.Type)
}
kin-openapi-0.124.0/openapi3/loader_relative_refs_test.go 0000664 0000000 0000000 00000066144 14604223742 0023373 0 ustar 00root root 0000000 0000000 package openapi3
import (
"fmt"
"net/url"
"testing"
"github.com/stretchr/testify/require"
)
type refTestDataEntry struct {
name string
contentTemplate string
testFunc func(t *testing.T, doc *T)
}
type refTestDataEntryWithErrorMessage struct {
name string
contentTemplate string
errorMessage *string
testFunc func(t *testing.T, doc *T)
}
var refTestDataEntries = []refTestDataEntry{
{
name: "SchemaRef",
contentTemplate: externalSchemaRefTemplate,
testFunc: func(t *testing.T, doc *T) {
require.NotNil(t, doc.Components.Schemas["TestSchema"].Value.Type)
require.Equal(t, &Types{"string"}, doc.Components.Schemas["TestSchema"].Value.Type)
},
},
{
name: "ResponseRef",
contentTemplate: externalResponseRefTemplate,
testFunc: func(t *testing.T, doc *T) {
desc := "description"
require.Equal(t, &desc, doc.Components.Responses["TestResponse"].Value.Description)
},
},
{
name: "ParameterRef",
contentTemplate: externalParameterRefTemplate,
testFunc: func(t *testing.T, doc *T) {
require.NotNil(t, doc.Components.Parameters["TestParameter"].Value.Name)
require.Equal(t, "id", doc.Components.Parameters["TestParameter"].Value.Name)
},
},
{
name: "ExampleRef",
contentTemplate: externalExampleRefTemplate,
testFunc: func(t *testing.T, doc *T) {
require.NotNil(t, doc.Components.Examples["TestExample"].Value.Description)
require.Equal(t, "description", doc.Components.Examples["TestExample"].Value.Description)
},
},
{
name: "RequestBodyRef",
contentTemplate: externalRequestBodyRefTemplate,
testFunc: func(t *testing.T, doc *T) {
require.NotNil(t, doc.Components.RequestBodies["TestRequestBody"].Value.Content)
},
},
{
name: "SecuritySchemeRef",
contentTemplate: externalSecuritySchemeRefTemplate,
testFunc: func(t *testing.T, doc *T) {
require.NotNil(t, doc.Components.SecuritySchemes["TestSecurityScheme"].Value.Description)
require.Equal(t, "description", doc.Components.SecuritySchemes["TestSecurityScheme"].Value.Description)
},
},
{
name: "ExternalHeaderRef",
contentTemplate: externalHeaderRefTemplate,
testFunc: func(t *testing.T, doc *T) {
require.NotNil(t, doc.Components.Headers["TestHeader"].Value.Description)
require.Equal(t, "description", doc.Components.Headers["TestHeader"].Value.Description)
},
},
{
name: "PathParameterRef",
contentTemplate: externalPathParameterRefTemplate,
testFunc: func(t *testing.T, doc *T) {
require.NotNil(t, doc.Paths.Value("/test/{id}").Parameters[0].Value.Name)
require.Equal(t, "id", doc.Paths.Value("/test/{id}").Parameters[0].Value.Name)
},
},
{
name: "PathOperationParameterRef",
contentTemplate: externalPathOperationParameterRefTemplate,
testFunc: func(t *testing.T, doc *T) {
require.NotNil(t, doc.Paths.Value("/test/{id}").Get.Parameters[0].Value)
require.Equal(t, "id", doc.Paths.Value("/test/{id}").Get.Parameters[0].Value.Name)
},
},
{
name: "PathOperationRequestBodyRef",
contentTemplate: externalPathOperationRequestBodyRefTemplate,
testFunc: func(t *testing.T, doc *T) {
require.NotNil(t, doc.Paths.Value("/test").Post.RequestBody.Value)
require.NotNil(t, doc.Paths.Value("/test").Post.RequestBody.Value.Content)
},
},
{
name: "PathOperationResponseRef",
contentTemplate: externalPathOperationResponseRefTemplate,
testFunc: func(t *testing.T, doc *T) {
require.NotNil(t, doc.Paths.Value("/test").Post.Responses.Default().Value)
desc := "description"
require.Equal(t, &desc, doc.Paths.Value("/test").Post.Responses.Default().Value.Description)
},
},
{
name: "PathOperationParameterSchemaRef",
contentTemplate: externalPathOperationParameterSchemaRefTemplate,
testFunc: func(t *testing.T, doc *T) {
require.NotNil(t, doc.Paths.Value("/test/{id}").Get.Parameters[0].Value.Schema.Value)
require.Equal(t, &Types{"string"}, doc.Paths.Value("/test/{id}").Get.Parameters[0].Value.Schema.Value.Type)
require.Equal(t, "id", doc.Paths.Value("/test/{id}").Get.Parameters[0].Value.Name)
},
},
{
name: "PathOperationParameterRefWithContentInQuery",
contentTemplate: externalPathOperationParameterWithContentInQueryTemplate,
testFunc: func(t *testing.T, doc *T) {
schemaRef := doc.Paths.Value("/test/{id}").Get.Parameters[0].Value.Content["application/json"].Schema
require.NotNil(t, schemaRef.Value)
require.Equal(t, &Types{"string"}, schemaRef.Value.Type)
},
},
{
name: "PathOperationRequestBodyExampleRef",
contentTemplate: externalPathOperationRequestBodyExampleRefTemplate,
testFunc: func(t *testing.T, doc *T) {
require.NotNil(t, doc.Paths.Value("/test").Post.RequestBody.Value.Content["application/json"].Examples["application/json"].Value)
require.Equal(t, "description", doc.Paths.Value("/test").Post.RequestBody.Value.Content["application/json"].Examples["application/json"].Value.Description)
},
},
{
name: "PathOperationRequestBodyContentSchemaRef",
contentTemplate: externalPathOperationRequestBodyContentSchemaRefTemplate,
testFunc: func(t *testing.T, doc *T) {
require.NotNil(t, doc.Paths.Value("/test").Post.RequestBody.Value.Content["application/json"].Schema.Value)
require.Equal(t, &Types{"string"}, doc.Paths.Value("/test").Post.RequestBody.Value.Content["application/json"].Schema.Value.Type)
},
},
{
name: "PathOperationResponseExampleRef",
contentTemplate: externalPathOperationResponseExampleRefTemplate,
testFunc: func(t *testing.T, doc *T) {
require.NotNil(t, doc.Paths.Value("/test").Post.Responses.Default().Value)
desc := "testdescription"
require.Equal(t, &desc, doc.Paths.Value("/test").Post.Responses.Default().Value.Description)
require.Equal(t, "description", doc.Paths.Value("/test").Post.Responses.Default().Value.Content["application/json"].Examples["application/json"].Value.Description)
},
},
{
name: "PathOperationResponseSchemaRef",
contentTemplate: externalPathOperationResponseSchemaRefTemplate,
testFunc: func(t *testing.T, doc *T) {
require.NotNil(t, doc.Paths.Value("/test").Post.Responses.Default().Value)
desc := "testdescription"
require.Equal(t, &desc, doc.Paths.Value("/test").Post.Responses.Default().Value.Description)
require.Equal(t, &Types{"string"}, doc.Paths.Value("/test").Post.Responses.Default().Value.Content["application/json"].Schema.Value.Type)
},
},
{
name: "ComponentHeaderSchemaRef",
contentTemplate: externalComponentHeaderSchemaRefTemplate,
testFunc: func(t *testing.T, doc *T) {
require.NotNil(t, doc.Components.Headers["TestHeader"].Value)
require.Equal(t, &Types{"string"}, doc.Components.Headers["TestHeader"].Value.Schema.Value.Type)
},
},
{
name: "RequestResponseHeaderRef",
contentTemplate: externalRequestResponseHeaderRefTemplate,
testFunc: func(t *testing.T, doc *T) {
require.NotNil(t, doc.Paths.Value("/test").Post.Responses.Default().Value.Headers["X-TEST-HEADER"].Value.Description)
require.Equal(t, "description", doc.Paths.Value("/test").Post.Responses.Default().Value.Headers["X-TEST-HEADER"].Value.Description)
},
},
}
var refTestDataEntriesResponseError = []refTestDataEntryWithErrorMessage{
{
name: "CannotContainBothSchemaAndContentInAParameter",
contentTemplate: externalCannotContainBothSchemaAndContentInAParameter,
errorMessage: &(&struct{ x string }{"cannot contain both schema and content in a parameter"}).x,
testFunc: func(t *testing.T, doc *T) {
},
},
}
func TestLoadFromDataWithExternalRef(t *testing.T) {
for _, td := range refTestDataEntries {
t.Logf("testcase %q", td.name)
spec := []byte(fmt.Sprintf(td.contentTemplate, "components.openapi.json"))
loader := NewLoader()
loader.IsExternalRefsAllowed = true
doc, err := loader.LoadFromDataWithPath(spec, &url.URL{Path: "testdata/testfilename.openapi.json"})
require.NoError(t, err)
td.testFunc(t, doc)
}
}
func TestLoadFromDataWithExternalRefResponseError(t *testing.T) {
for _, td := range refTestDataEntriesResponseError {
t.Logf("testcase %q", td.name)
spec := []byte(fmt.Sprintf(td.contentTemplate, "components.openapi.json"))
loader := NewLoader()
loader.IsExternalRefsAllowed = true
doc, err := loader.LoadFromDataWithPath(spec, &url.URL{Path: "testdata/testfilename.openapi.json"})
require.EqualError(t, err, *td.errorMessage)
td.testFunc(t, doc)
}
}
func TestLoadFromDataWithExternalNestedRef(t *testing.T) {
for _, td := range refTestDataEntries {
t.Logf("testcase %q", td.name)
spec := []byte(fmt.Sprintf(td.contentTemplate, "nesteddir/nestedcomponents.openapi.json"))
loader := NewLoader()
loader.IsExternalRefsAllowed = true
doc, err := loader.LoadFromDataWithPath(spec, &url.URL{Path: "testdata/testfilename.openapi.json"})
require.NoError(t, err)
td.testFunc(t, doc)
}
}
const externalSchemaRefTemplate = `
{
"openapi": "3.0.0",
"info": {
"title": "",
"version": "1"
},
"paths": {},
"components": {
"schemas": {
"TestSchema": {
"$ref": "%s#/components/schemas/CustomTestSchema"
}
}
}
}`
const externalResponseRefTemplate = `
{
"openapi": "3.0.0",
"info": {
"title": "",
"version": "1"
},
"paths": {},
"components": {
"responses": {
"TestResponse": {
"$ref": "%s#/components/responses/CustomTestResponse"
}
}
}
}`
const externalParameterRefTemplate = `
{
"openapi": "3.0.0",
"info": {
"title": "",
"version": "1"
},
"paths": {},
"components": {
"parameters": {
"TestParameter": {
"$ref": "%s#/components/parameters/CustomTestParameter"
}
}
}
}`
const externalExampleRefTemplate = `
{
"openapi": "3.0.0",
"info": {
"title": "",
"version": "1"
},
"paths": {},
"components": {
"examples": {
"TestExample": {
"$ref": "%s#/components/examples/CustomTestExample"
}
}
}
}`
const externalRequestBodyRefTemplate = `
{
"openapi": "3.0.0",
"info": {
"title": "",
"version": "1"
},
"paths": {},
"components": {
"requestBodies": {
"TestRequestBody": {
"$ref": "%s#/components/requestBodies/CustomTestRequestBody"
}
}
}
}`
const externalSecuritySchemeRefTemplate = `
{
"openapi": "3.0.0",
"info": {
"title": "",
"version": "1"
},
"paths": {},
"components": {
"securitySchemes": {
"TestSecurityScheme": {
"$ref": "%s#/components/securitySchemes/CustomTestSecurityScheme"
}
}
}
}`
const externalHeaderRefTemplate = `
{
"openapi": "3.0.0",
"info": {
"title": "",
"version": "1"
},
"paths": {},
"components": {
"headers": {
"TestHeader": {
"$ref": "%s#/components/headers/CustomTestHeader"
}
}
}
}`
const externalPathParameterRefTemplate = `
{
"openapi": "3.0.0",
"info": {
"title": "",
"version": "1"
},
"paths": {
"/test/{id}": {
"parameters": [
{
"$ref": "%s#/components/parameters/CustomTestParameter"
}
]
}
}
}`
const externalPathOperationParameterRefTemplate = `
{
"openapi": "3.0.0",
"info": {
"title": "",
"version": "1"
},
"paths": {
"/test/{id}": {
"get": {
"responses": {},
"parameters": [
{
"$ref": "%s#/components/parameters/CustomTestParameter"
}
]
}
}
}
}`
const externalPathOperationRequestBodyRefTemplate = `
{
"openapi": "3.0.0",
"info": {
"title": "",
"version": "1"
},
"paths": {
"/test": {
"post": {
"responses": {},
"requestBody": {
"$ref": "%s#/components/requestBodies/CustomTestRequestBody"
}
}
}
}
}`
const externalPathOperationResponseRefTemplate = `
{
"openapi": "3.0.0",
"info": {
"title": "",
"version": "1"
},
"paths": {
"/test": {
"post": {
"responses": {
"default": {
"$ref": "%s#/components/responses/CustomTestResponse"
}
}
}
}
}
}`
const externalPathOperationParameterSchemaRefTemplate = `
{
"openapi": "3.0.0",
"info": {
"title": "",
"version": "1"
},
"paths": {
"/test/{id}": {
"get": {
"responses": {},
"parameters": [
{
"$ref": "#/components/parameters/CustomTestParameter"
}
]
}
}
},
"components": {
"parameters": {
"CustomTestParameter": {
"name": "id",
"in": "header",
"schema": {
"$ref": "%s#/components/schemas/CustomTestSchema"
}
}
}
}
}`
const externalPathOperationParameterWithContentInQueryTemplate = `
{
"openapi": "3.0.0",
"info": {
"title": "",
"version": "1"
},
"paths": {
"/test/{id}": {
"get": {
"responses": {},
"parameters": [
{
"$ref": "#/components/parameters/CustomTestParameter"
}
]
}
}
},
"components": {
"parameters": {
"CustomTestParameter": {
"content": {
"application/json": {
"schema": {
"$ref": "%s#/components/schemas/CustomTestSchema"
}
}
}
}
}
}
}`
const externalCannotContainBothSchemaAndContentInAParameter = `
{
"openapi": "3.0.0",
"info": {
"title": "",
"version": "1"
},
"paths": {
"/test/{id}": {
"get": {
"responses": {},
"parameters": [
{
"$ref": "#/components/parameters/CustomTestParameter"
}
]
}
}
},
"components": {
"parameters": {
"CustomTestParameter": {
"content": {
"application/json": {
"schema": {
"$ref": "%s#/components/schemas/CustomTestSchema"
}
}
},
"schema": {
"$ref": "%s#/components/schemas/CustomTestSchema"
}
}
}
}
}`
const externalPathOperationRequestBodyExampleRefTemplate = `
{
"openapi": "3.0.0",
"info": {
"title": "",
"version": "1"
},
"paths": {
"/test": {
"post": {
"responses": {},
"requestBody": {
"$ref": "#/components/requestBodies/CustomTestRequestBody"
}
}
}
},
"components": {
"requestBodies": {
"CustomTestRequestBody": {
"content": {
"application/json": {
"examples": {
"application/json": {
"$ref": "%s#/components/examples/CustomTestExample"
}
}
}
}
}
}
}
}`
const externalPathOperationRequestBodyContentSchemaRefTemplate = `
{
"openapi": "3.0.0",
"info": {
"title": "",
"version": "1"
},
"paths": {
"/test": {
"post": {
"responses": {},
"requestBody": {
"$ref": "#/components/requestBodies/CustomTestRequestBody"
}
}
}
},
"components": {
"requestBodies": {
"CustomTestRequestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "%s#/components/schemas/CustomTestSchema"
}
}
}
}
}
}
}`
const externalPathOperationResponseExampleRefTemplate = `
{
"openapi": "3.0.0",
"info": {
"title": "",
"version": "1"
},
"paths": {
"/test": {
"post": {
"responses": {
"default": {
"$ref": "#/components/responses/CustomTestResponse"
}
}
}
}
},
"components": {
"responses": {
"CustomTestResponse": {
"description": "testdescription",
"content": {
"application/json": {
"examples": {
"application/json": {
"$ref": "%s#/components/examples/CustomTestExample"
}
}
}
}
}
}
}
}`
const externalPathOperationResponseSchemaRefTemplate = `
{
"openapi": "3.0.0",
"info": {
"title": "",
"version": "1"
},
"paths": {
"/test": {
"post": {
"responses": {
"default": {
"$ref": "#/components/responses/CustomTestResponse"
}
}
}
}
},
"components": {
"responses": {
"CustomTestResponse": {
"description": "testdescription",
"content": {
"application/json": {
"schema": {
"$ref": "%s#/components/schemas/CustomTestSchema"
}
}
}
}
}
}
}`
const externalComponentHeaderSchemaRefTemplate = `
{
"openapi": "3.0.0",
"info": {
"title": "",
"version": "1"
},
"paths": {},
"components": {
"headers": {
"TestHeader": {
"$ref": "#/components/headers/CustomTestHeader"
},
"CustomTestHeader": {
"name": "X-TEST-HEADER",
"in": "header",
"schema": {
"$ref": "%s#/components/schemas/CustomTestSchema"
}
}
}
}
}`
const externalRequestResponseHeaderRefTemplate = `
{
"openapi": "3.0.0",
"info": {
"title": "",
"version": "1"
},
"paths": {
"/test": {
"post": {
"responses": {
"default": {
"description": "test",
"headers": {
"X-TEST-HEADER": {
"$ref": "%s#/components/headers/CustomTestHeader"
}
}
}
}
}
}
}
}`
// Relative Schema Documents Tests
var relativeDocRefsTestDataEntries = []refTestDataEntry{
{
name: "SchemaRef",
contentTemplate: relativeSchemaDocsRefTemplate,
testFunc: func(t *testing.T, doc *T) {
require.NotNil(t, doc.Components.Schemas["TestSchema"].Value.Type)
require.Equal(t, &Types{"string"}, doc.Components.Schemas["TestSchema"].Value.Type)
},
},
{
name: "ResponseRef",
contentTemplate: relativeResponseDocsRefTemplate,
testFunc: func(t *testing.T, doc *T) {
desc := "description"
require.Equal(t, &desc, doc.Components.Responses["TestResponse"].Value.Description)
},
},
{
name: "ParameterRef",
contentTemplate: relativeParameterDocsRefTemplate,
testFunc: func(t *testing.T, doc *T) {
require.NotNil(t, doc.Components.Parameters["TestParameter"].Value.Name)
require.Equal(t, "param", doc.Components.Parameters["TestParameter"].Value.Name)
require.Equal(t, true, doc.Components.Parameters["TestParameter"].Value.Required)
},
},
{
name: "ExampleRef",
contentTemplate: relativeExampleDocsRefTemplate,
testFunc: func(t *testing.T, doc *T) {
require.NotNil(t, "param", doc.Components.Examples["TestExample"].Value.Summary)
require.NotNil(t, "param", doc.Components.Examples["TestExample"].Value.Value)
require.Equal(t, "An example", doc.Components.Examples["TestExample"].Value.Summary)
},
},
{
name: "RequestRef",
contentTemplate: relativeRequestDocsRefTemplate,
testFunc: func(t *testing.T, doc *T) {
require.NotNil(t, "param", doc.Components.RequestBodies["TestRequestBody"].Value.Description)
require.Equal(t, "example request", doc.Components.RequestBodies["TestRequestBody"].Value.Description)
},
},
{
name: "HeaderRef",
contentTemplate: relativeHeaderDocsRefTemplate,
testFunc: func(t *testing.T, doc *T) {
require.NotNil(t, "param", doc.Components.Headers["TestHeader"].Value.Description)
require.Equal(t, "description", doc.Components.Headers["TestHeader"].Value.Description)
},
},
{
name: "HeaderRef",
contentTemplate: relativeHeaderDocsRefTemplate,
testFunc: func(t *testing.T, doc *T) {
require.NotNil(t, "param", doc.Components.Headers["TestHeader"].Value.Description)
require.Equal(t, "description", doc.Components.Headers["TestHeader"].Value.Description)
},
},
{
name: "SecuritySchemeRef",
contentTemplate: relativeSecuritySchemeDocsRefTemplate,
testFunc: func(t *testing.T, doc *T) {
require.NotNil(t, doc.Components.SecuritySchemes["TestSecurityScheme"].Value.Type)
require.NotNil(t, doc.Components.SecuritySchemes["TestSecurityScheme"].Value.Scheme)
require.Equal(t, "http", doc.Components.SecuritySchemes["TestSecurityScheme"].Value.Type)
require.Equal(t, "basic", doc.Components.SecuritySchemes["TestSecurityScheme"].Value.Scheme)
},
},
{
name: "PathRef",
contentTemplate: relativePathDocsRefTemplate,
testFunc: func(t *testing.T, doc *T) {
require.NotNil(t, doc.Paths.Value("/pets"))
require.NotNil(t, doc.Paths.Value("/pets").Get.Responses.Value("200"))
require.NotNil(t, doc.Paths.Value("/pets").Get.Responses.Value("200").Value.Content["application/json"])
},
},
}
func TestLoadSpecWithRelativeDocumentRefs(t *testing.T) {
for _, td := range relativeDocRefsTestDataEntries {
t.Run(td.name, func(t *testing.T) {
spec := []byte(td.contentTemplate)
loader := NewLoader()
loader.IsExternalRefsAllowed = true
doc, err := loader.LoadFromDataWithPath(spec, &url.URL{Path: "testdata/"})
require.NoError(t, err)
td.testFunc(t, doc)
})
}
}
const relativeSchemaDocsRefTemplate = `
openapi: 3.0.0
info:
title: ""
version: "1.0"
paths: {}
components:
schemas:
TestSchema:
$ref: relativeDocs/CustomTestSchema.yml
`
const relativeResponseDocsRefTemplate = `
openapi: 3.0.0
info:
title: ""
version: "1.0"
paths: {}
components:
responses:
TestResponse:
$ref: relativeDocs/CustomTestResponse.yml
`
const relativeParameterDocsRefTemplate = `
openapi: 3.0.0
info:
title: ""
version: "1.0"
paths: {}
components:
parameters:
TestParameter:
$ref: relativeDocs/CustomTestParameter.yml
`
const relativeExampleDocsRefTemplate = `
openapi: 3.0.0
info:
title: ""
version: "1.0"
paths: {}
components:
examples:
TestExample:
$ref: relativeDocs/CustomTestExample.yml
`
const relativeRequestDocsRefTemplate = `
openapi: 3.0.0
info:
title: ""
version: "1.0"
paths: {}
components:
requestBodies:
TestRequestBody:
$ref: relativeDocs/CustomTestRequestBody.yml
`
const relativeHeaderDocsRefTemplate = `
openapi: 3.0.0
info:
title: ""
version: "1.0"
paths: {}
components:
headers:
TestHeader:
$ref: relativeDocs/CustomTestHeader.yml
`
const relativeSecuritySchemeDocsRefTemplate = `
openapi: 3.0.0
info:
title: ""
version: "1.0"
paths: {}
components:
securitySchemes:
TestSecurityScheme:
$ref: relativeDocs/CustomTestSecurityScheme.yml
`
const relativePathDocsRefTemplate = `
openapi: 3.0.0
info:
title: ""
version: "2.0"
paths:
/pets:
$ref: relativeDocs/CustomTestPath.yml
`
func TestLoadSpecWithRelativeDocumentRefs2(t *testing.T) {
loader := NewLoader()
loader.IsExternalRefsAllowed = true
doc, err := loader.LoadFromFile("testdata/relativeDocsUseDocumentPath/openapi/openapi.yml")
require.NoError(t, err)
// path in nested directory
// check parameter
nestedDirPath := doc.Paths.Value("/pets/{id}")
require.Equal(t, "param", nestedDirPath.Patch.Parameters[0].Value.Name)
require.Equal(t, "path", nestedDirPath.Patch.Parameters[0].Value.In)
require.Equal(t, true, nestedDirPath.Patch.Parameters[0].Value.Required)
// check header
require.Equal(t, "header", nestedDirPath.Patch.Responses.Value("200").Value.Headers["X-Rate-Limit-Reset"].Value.Description)
require.Equal(t, "header1", nestedDirPath.Patch.Responses.Value("200").Value.Headers["X-Another"].Value.Description)
require.Equal(t, "header2", nestedDirPath.Patch.Responses.Value("200").Value.Headers["X-And-Another"].Value.Description)
// check request body
require.Equal(t, "example request", nestedDirPath.Patch.RequestBody.Value.Description)
// check response schema and example
require.Equal(t, nestedDirPath.Patch.Responses.Value("200").Value.Content["application/json"].Schema.Value.Type, &Types{"string"})
expectedExample := "hello"
require.Equal(t, expectedExample, nestedDirPath.Patch.Responses.Value("200").Value.Content["application/json"].Examples["CustomTestExample"].Value.Value)
// path in more nested directory
// check parameter
moreNestedDirPath := doc.Paths.Value("/pets/{id}/{city}")
require.Equal(t, "param", moreNestedDirPath.Patch.Parameters[0].Value.Name)
require.Equal(t, "path", moreNestedDirPath.Patch.Parameters[0].Value.In)
require.Equal(t, true, moreNestedDirPath.Patch.Parameters[0].Value.Required)
// check header
require.Equal(t, "header", nestedDirPath.Patch.Responses.Value("200").Value.Headers["X-Rate-Limit-Reset"].Value.Description)
require.Equal(t, "header1", nestedDirPath.Patch.Responses.Value("200").Value.Headers["X-Another"].Value.Description)
require.Equal(t, "header2", nestedDirPath.Patch.Responses.Value("200").Value.Headers["X-And-Another"].Value.Description)
// check request body
require.Equal(t, "example request", moreNestedDirPath.Patch.RequestBody.Value.Description)
// check response schema and example
require.Equal(t, &Types{"string"}, moreNestedDirPath.Patch.Responses.Value("200").Value.Content["application/json"].Schema.Value.Type)
require.Equal(t, moreNestedDirPath.Patch.Responses.Value("200").Value.Content["application/json"].Examples["CustomTestExample"].Value.Value, expectedExample)
}
kin-openapi-0.124.0/openapi3/loader_test.go 0000664 0000000 0000000 00000043622 14604223742 0020455 0 ustar 00root root 0000000 0000000 package openapi3
import (
"bytes"
"fmt"
"net"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
const addr = "localhost:7965"
func TestLoadYAML(t *testing.T) {
spec := []byte(`
openapi: 3.0.0
info:
title: An API
version: v1
components:
schemas:
NewItem:
required: [name]
properties:
name: {type: string}
tag: {type: string}
ErrorModel:
type: object
required: [code, message]
properties:
code: {type: integer}
message: {type: string}
paths:
/items:
put:
description: ''
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/NewItem'
responses:
default: &defaultResponse # a YAML ref
description: unexpected error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorModel'
`)
loader := NewLoader()
doc, err := loader.LoadFromData(spec)
require.NoError(t, err)
require.Equal(t, "An API", doc.Info.Title)
require.Equal(t, 2, len(doc.Components.Schemas))
require.Equal(t, 1, doc.Paths.Len())
require.Equal(t, "unexpected error", *doc.Paths.Value("/items").Put.Responses.Default().Value.Description)
err = doc.Validate(loader.Context)
require.NoError(t, err)
}
func TestIssue731(t *testing.T) {
spec := []byte(`
openapi: 3.0.0
info:
title: An API
version: v1
paths:
/items:
put:
description: ''
requestBody:
required: true
# Note mis-indented content block
content:
application/json:
schema:
type: object
responses:
default:
description: unexpected error
content:
application/json:
schema:
type: object
`[1:])
loader := NewLoader()
doc, err := loader.LoadFromData(spec)
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.ErrorContains(t, err, `content of the request body is required`)
}
func ExampleLoader() {
spec := []byte(`
openapi: 3.0.1
paths: {}
info:
version: 1.1.1
title: title
description: An API
`[1:])
loader := NewLoader()
doc, err := loader.LoadFromData(spec)
if err != nil {
panic(err)
}
if err := doc.Validate(loader.Context); err != nil {
panic(err)
}
fmt.Print(doc.Info.Description)
// Output: An API
}
func TestResolveSchemaRef(t *testing.T) {
source := []byte(`{"openapi":"3.0.0","info":{"title":"MyAPI","version":"0.1","description":"An API"},"paths":{},"components":{"schemas":{"B":{"type":"string"},"A":{"allOf":[{"$ref":"#/components/schemas/B"}]}}}}`)
loader := NewLoader()
doc, err := loader.LoadFromData(source)
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
refAVisited := doc.Components.Schemas["A"].Value.AllOf[0]
require.Equal(t, "#/components/schemas/B", refAVisited.Ref)
require.NotNil(t, refAVisited.Value)
}
func TestResolveResponseExampleRef(t *testing.T) {
source := []byte(`
openapi: 3.0.1
info:
title: My API
version: 1.0.0
components:
examples:
test:
value:
error: false
paths:
/:
get:
responses:
200:
description: A test response
content:
application/json:
examples:
test:
$ref: '#/components/examples/test'`)
loader := NewLoader()
doc, err := loader.LoadFromData(source)
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
example := doc.Paths.Value("/").Get.Responses.Status(200).Value.Content.Get("application/json").Examples["test"]
require.NotNil(t, example.Value)
require.Equal(t, example.Value.Value.(map[string]interface{})["error"].(bool), false)
}
func TestLoadErrorOnRefMisuse(t *testing.T) {
spec := []byte(`
openapi: '3.0.0'
servers: [{url: /}]
info:
title: Some API
version: '1'
components:
schemas:
Thing: {type: string}
paths:
/items:
put:
description: ''
requestBody:
# Uses a schema ref instead of a requestBody ref.
$ref: '#/components/schemas/Thing'
responses:
'201':
description: ''
content:
application/json:
schema:
$ref: '#/components/schemas/Thing'
`)
loader := NewLoader()
_, err := loader.LoadFromData(spec)
require.Error(t, err)
}
func TestLoadPathParamRef(t *testing.T) {
spec := []byte(`
openapi: '3.0.0'
info:
title: ''
version: '1'
components:
parameters:
testParam:
name: test
in: query
schema:
type: string
paths:
'/':
parameters:
- $ref: '#/components/parameters/testParam'
get:
responses:
'200':
description: Test call.
`)
loader := NewLoader()
doc, err := loader.LoadFromData(spec)
require.NoError(t, err)
require.NotNil(t, doc.Paths.Value("/").Parameters[0].Value)
}
func TestLoadRequestExampleRef(t *testing.T) {
spec := []byte(`
openapi: '3.0.0'
info:
title: ''
version: '1'
components:
examples:
test:
value:
hello: world
paths:
'/':
post:
requestBody:
content:
application/json:
examples:
test:
$ref: "#/components/examples/test"
responses:
'200':
description: Test call.
`)
loader := NewLoader()
doc, err := loader.LoadFromData(spec)
require.NoError(t, err)
require.NotNil(t, doc.Paths.Value("/").Post.RequestBody.Value.Content.Get("application/json").Examples["test"])
}
func createTestServer(t *testing.T, handler http.Handler) *httptest.Server {
ts := httptest.NewUnstartedServer(handler)
l, err := net.Listen("tcp", addr)
require.NoError(t, err)
ts.Listener.Close()
ts.Listener = l
return ts
}
func TestLoadFromRemoteURL(t *testing.T) {
fs := http.FileServer(http.Dir("testdata"))
ts := createTestServer(t, fs)
ts.Start()
defer ts.Close()
loader := NewLoader()
loader.IsExternalRefsAllowed = true
url, err := url.Parse("http://" + addr + "/test.openapi.json")
require.NoError(t, err)
doc, err := loader.LoadFromURI(url)
require.NoError(t, err)
require.Equal(t, &Types{"string"}, doc.Components.Schemas["TestSchema"].Value.Type)
}
func TestLoadWithReferenceInReference(t *testing.T) {
loader := NewLoader()
loader.IsExternalRefsAllowed = true
doc, err := loader.LoadFromFile("testdata/refInRef/openapi.json")
require.NoError(t, err)
require.NotNil(t, doc)
err = doc.Validate(loader.Context)
require.NoError(t, err)
require.Equal(t, &Types{"string"}, doc.Paths.Value("/api/test/ref/in/ref").Post.RequestBody.Value.Content["application/json"].Schema.Value.Properties["definition_reference"].Value.Type)
}
func TestLoadWithRecursiveReferenceInLocalReferenceInParentSubdir(t *testing.T) {
loader := NewLoader()
loader.IsExternalRefsAllowed = true
doc, err := loader.LoadFromFile("testdata/refInLocalRefInParentsSubdir/spec/openapi.json")
require.NoError(t, err)
require.NotNil(t, doc)
err = doc.Validate(loader.Context)
require.NoError(t, err)
require.Equal(t, &Types{"object"}, doc.Paths.Value("/api/test/ref/in/ref").Post.RequestBody.Value.Content["application/json"].Schema.Value.Properties["definition_reference"].Value.Type)
}
func TestLoadWithRecursiveReferenceInReferenceInLocalReference(t *testing.T) {
loader := NewLoader()
loader.IsExternalRefsAllowed = true
doc, err := loader.LoadFromFile("testdata/refInLocalRef/openapi.json")
require.NoError(t, err)
require.NotNil(t, doc)
err = doc.Validate(loader.Context)
require.NoError(t, err)
require.Equal(t, &Types{"integer"}, doc.Paths.Value("/api/test/ref/in/ref").Post.RequestBody.Value.Content["application/json"].Schema.Value.Properties["data"].Value.Properties["definition_reference"].Value.Properties["ref_prop_part"].Value.Properties["idPart"].Value.Type)
require.Equal(t, "int64", doc.Paths.Value("/api/test/ref/in/ref").Post.RequestBody.Value.Content["application/json"].Schema.Value.Properties["data"].Value.Properties["definition_reference"].Value.Properties["ref_prop_part"].Value.Properties["idPart"].Value.Format)
}
func TestLoadWithReferenceInReferenceInProperty(t *testing.T) {
loader := NewLoader()
loader.IsExternalRefsAllowed = true
doc, err := loader.LoadFromFile("testdata/refInRefInProperty/openapi.yaml")
require.NoError(t, err)
require.NotNil(t, doc)
err = doc.Validate(loader.Context)
require.NoError(t, err)
require.Equal(t, "Problem details", doc.Paths.Value("/api/test/ref/in/ref/in/property").Post.Responses.Value("401").Value.Content["application/json"].Schema.Value.Properties["error"].Value.Title)
}
func TestLoadFileWithExternalSchemaRef(t *testing.T) {
loader := NewLoader()
loader.IsExternalRefsAllowed = true
doc, err := loader.LoadFromFile("testdata/testref.openapi.json")
require.NoError(t, err)
require.NotNil(t, doc.Components.Schemas["AnotherTestSchema"].Value.Type)
}
func TestLoadFileWithExternalSchemaRefSingleComponent(t *testing.T) {
loader := NewLoader()
loader.IsExternalRefsAllowed = true
doc, err := loader.LoadFromFile("testdata/testrefsinglecomponent.openapi.json")
require.NoError(t, err)
require.NotNil(t, doc.Components.Responses["SomeResponse"])
desc := "this is a single response definition"
require.Equal(t, &desc, doc.Components.Responses["SomeResponse"].Value.Description)
}
func TestLoadRequestResponseHeaderRef(t *testing.T) {
spec := []byte(`
{
"openapi": "3.0.0",
"info": {
"title": "",
"version": "1"
},
"paths": {
"/test": {
"post": {
"responses": {
"default": {
"description": "test",
"headers": {
"X-TEST-HEADER": {
"$ref": "#/components/headers/TestHeader"
}
}
}
}
}
}
},
"components": {
"headers": {
"TestHeader": {
"description": "testheader"
}
}
}
}`)
loader := NewLoader()
doc, err := loader.LoadFromData(spec)
require.NoError(t, err)
require.NotNil(t, doc.Paths.Value("/test").Post.Responses.Default().Value.Headers["X-TEST-HEADER"].Value.Description)
require.Equal(t, "testheader", doc.Paths.Value("/test").Post.Responses.Default().Value.Headers["X-TEST-HEADER"].Value.Description)
}
func TestLoadFromDataWithExternalRequestResponseHeaderRemoteRef(t *testing.T) {
spec := []byte(`
{
"openapi": "3.0.0",
"info": {
"title": "",
"version": "1"
},
"paths": {
"/test": {
"post": {
"responses": {
"default": {
"description": "test",
"headers": {
"X-TEST-HEADER": {
"$ref": "http://` + addr + `/components.openapi.json#/components/headers/CustomTestHeader"
}
}
}
}
}
}
}
}`)
fs := http.FileServer(http.Dir("testdata"))
ts := createTestServer(t, fs)
ts.Start()
defer ts.Close()
loader := NewLoader()
loader.IsExternalRefsAllowed = true
doc, err := loader.LoadFromDataWithPath(spec, &url.URL{Path: "testdata/testfilename.openapi.json"})
require.NoError(t, err)
require.NotNil(t, doc.Paths.Value("/test").Post.Responses.Default().Value.Headers["X-TEST-HEADER"].Value.Description)
require.Equal(t, "description", doc.Paths.Value("/test").Post.Responses.Default().Value.Headers["X-TEST-HEADER"].Value.Description)
}
func TestLoadYamlFile(t *testing.T) {
loader := NewLoader()
loader.IsExternalRefsAllowed = true
doc, err := loader.LoadFromFile("testdata/test.openapi.yml")
require.NoError(t, err)
require.Equal(t, "OAI Specification in YAML", doc.Info.Title)
}
func TestLoadYamlFileWithExternalSchemaRef(t *testing.T) {
loader := NewLoader()
loader.IsExternalRefsAllowed = true
doc, err := loader.LoadFromFile("testdata/testref.openapi.yml")
require.NoError(t, err)
require.NotNil(t, doc.Components.Schemas["AnotherTestSchema"].Value.Type)
}
func TestLoadYamlFileWithExternalPathRef(t *testing.T) {
loader := NewLoader()
loader.IsExternalRefsAllowed = true
doc, err := loader.LoadFromFile("testdata/pathref.openapi.yml")
require.NoError(t, err)
require.NotNil(t, doc.Paths.Value("/test").Get.Responses.Value("200").Value.Content["application/json"].Schema.Value.Type)
require.Equal(t, &Types{"string"}, doc.Paths.Value("/test").Get.Responses.Value("200").Value.Content["application/json"].Schema.Value.Type)
}
func TestResolveResponseLinkRef(t *testing.T) {
source := []byte(`
openapi: 3.0.1
info:
title: My API
version: 1.0.0
components:
links:
Father:
description: link to to the father
operationId: getUserById
parameters:
"id": "$response.body#/fatherId"
paths:
/users/{id}:
get:
operationId: getUserById,
parameters:
- name: id,
in: path
required: true
schema:
type: string
responses:
200:
description: A test response
content:
application/json:
links:
father:
$ref: '#/components/links/Father'
`)
loader := NewLoader()
doc, err := loader.LoadFromData(source)
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
response := doc.Paths.Value("/users/{id}").Get.Responses.Status(200).Value
link := response.Links[`father`].Value
require.NotNil(t, link)
require.Equal(t, "getUserById", link.OperationID)
require.Equal(t, "link to to the father", link.Description)
}
func TestLinksFromOAISpec(t *testing.T) {
loader := NewLoader()
doc, err := loader.LoadFromFile("testdata/link-example.yaml")
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
response := doc.Paths.Value("/2.0/repositories/{username}/{slug}").Get.Responses.Status(200).Value
link := response.Links[`repositoryPullRequests`].Value
require.Equal(t, map[string]interface{}{
"username": "$response.body#/owner/username",
"slug": "$response.body#/slug",
}, link.Parameters)
}
func TestResolveNonComponentsRef(t *testing.T) {
spec := []byte(`
openapi: 3.0.0
info:
title: An API
version: v1
components:
schemas:
NewItem:
required: [name]
properties:
name: {type: string}
tag: {type: string}
ErrorModel:
type: object
required: [code, message]
properties:
code: {type: integer}
message: {type: string}
paths:
/items:
put:
description: ''
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/NewItem'
responses:
default:
description: unexpected error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorModel'
post:
description: ''
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/paths/~1items/put/requestBody/content/application~1json/schema'
responses:
default:
description: unexpected error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorModel'
`)
loader := NewLoader()
doc, err := loader.LoadFromData(spec)
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
}
func TestServersVariables(t *testing.T) {
const spec = `
openapi: 3.0.1
info:
title: My API
version: 1.0.0
paths: {}
servers:
- @@@
`
for value, expected := range map[string]string{
`{url: /}`: "",
`{url: "http://{x}.{y}.example.com"}`: "invalid servers: server has undeclared variables",
`{url: "http://{x}.y}.example.com"}`: "invalid servers: server URL has mismatched { and }",
`{url: "http://{x.example.com"}`: "invalid servers: server URL has mismatched { and }",
`{url: "http://{x}.example.com", variables: {x: {default: "www"}}}`: "",
`{url: "http://{x}.example.com", variables: {x: {default: "www", enum: ["www"]}}}`: "",
`{url: "http://{x}.example.com", variables: {x: {enum: ["www"]}}}`: `invalid servers: field default is required in {"enum":["www"]}`,
`{url: "http://www.example.com", variables: {x: {enum: ["www"]}}}`: "invalid servers: server has undeclared variables",
`{url: "http://{y}.example.com", variables: {x: {enum: ["www"]}}}`: "invalid servers: server has undeclared variables",
} {
t.Run(value, func(t *testing.T) {
loader := NewLoader()
doc, err := loader.LoadFromData([]byte(strings.Replace(spec, "@@@", value, 1)))
require.NoError(t, err)
err = doc.Validate(loader.Context)
if expected == "" {
require.NoError(t, err)
} else {
require.EqualError(t, err, expected)
}
})
}
}
func TestReadFromIoReader(t *testing.T) {
buffer := bytes.NewReader([]byte(`openapi: 3.0.0
info:
title: An API
version: v1
components:
schemas:
NewItem:
required: [name]
properties:
name: {type: string}
tag: {type: string}
ErrorModel:
type: object
required: [code, message]
properties:
code: {type: integer}
message: {type: string}
paths:
/items:
put:
description: ''
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/NewItem'
responses:
default: &defaultResponse # a YAML ref
description: unexpected error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorModel'`))
loader := NewLoader()
doc, err := loader.LoadFromIoReader(buffer)
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
}
func TestReadFromIoReader_Nil(t *testing.T) {
loader := NewLoader()
_, err := loader.LoadFromIoReader(nil)
require.EqualError(t, err, "invalid reader: ")
}
kin-openapi-0.124.0/openapi3/loader_uri_reader.go 0000664 0000000 0000000 00000006641 14604223742 0021617 0 ustar 00root root 0000000 0000000 package openapi3
import (
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
"sync"
)
// ReadFromURIFunc defines a function which reads the contents of a resource
// located at a URI.
type ReadFromURIFunc func(loader *Loader, url *url.URL) ([]byte, error)
var uriMu = &sync.RWMutex{}
// ErrURINotSupported indicates the ReadFromURIFunc does not know how to handle a
// given URI.
var ErrURINotSupported = errors.New("unsupported URI")
// ReadFromURIs returns a ReadFromURIFunc which tries to read a URI using the
// given reader functions, in the same order. If a reader function does not
// support the URI and returns ErrURINotSupported, the next function is checked
// until a match is found, or the URI is not supported by any.
func ReadFromURIs(readers ...ReadFromURIFunc) ReadFromURIFunc {
return func(loader *Loader, url *url.URL) ([]byte, error) {
for i := range readers {
buf, err := readers[i](loader, url)
if err == ErrURINotSupported {
continue
} else if err != nil {
return nil, err
}
return buf, nil
}
return nil, ErrURINotSupported
}
}
// DefaultReadFromURI returns a caching ReadFromURIFunc which can read remote
// HTTP URIs and local file URIs.
var DefaultReadFromURI = URIMapCache(ReadFromURIs(ReadFromHTTP(http.DefaultClient), ReadFromFile))
// ReadFromHTTP returns a ReadFromURIFunc which uses the given http.Client to
// read the contents from a remote HTTP URI. This client may be customized to
// implement timeouts, RFC 7234 caching, etc.
func ReadFromHTTP(cl *http.Client) ReadFromURIFunc {
return func(loader *Loader, location *url.URL) ([]byte, error) {
if location.Scheme == "" || location.Host == "" {
return nil, ErrURINotSupported
}
req, err := http.NewRequest("GET", location.String(), nil)
if err != nil {
return nil, err
}
resp, err := cl.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode > 399 {
return nil, fmt.Errorf("error loading %q: request returned status code %d", location.String(), resp.StatusCode)
}
return io.ReadAll(resp.Body)
}
}
func is_file(location *url.URL) bool {
return location.Path != "" &&
location.Host == "" &&
(location.Scheme == "" || location.Scheme == "file")
}
// ReadFromFile is a ReadFromURIFunc which reads local file URIs.
func ReadFromFile(loader *Loader, location *url.URL) ([]byte, error) {
if !is_file(location) {
return nil, ErrURINotSupported
}
return os.ReadFile(location.Path)
}
// URIMapCache returns a ReadFromURIFunc that caches the contents read from URI
// locations in a simple map. This cache implementation is suitable for
// short-lived processes such as command-line tools which process OpenAPI
// documents.
func URIMapCache(reader ReadFromURIFunc) ReadFromURIFunc {
cache := map[string][]byte{}
return func(loader *Loader, location *url.URL) (buf []byte, err error) {
if location.Scheme == "" || location.Scheme == "file" {
if !filepath.IsAbs(location.Path) {
// Do not cache relative file paths; this can cause trouble if
// the current working directory changes when processing
// multiple top-level documents.
return reader(loader, location)
}
}
uri := location.String()
var ok bool
uriMu.RLock()
if buf, ok = cache[uri]; ok {
uriMu.RUnlock()
return
}
uriMu.RUnlock()
if buf, err = reader(loader, location); err != nil {
return
}
uriMu.Lock()
defer uriMu.Unlock()
cache[uri] = buf
return
}
}
kin-openapi-0.124.0/openapi3/maplike.go 0000664 0000000 0000000 00000021061 14604223742 0017563 0 ustar 00root root 0000000 0000000 package openapi3
import (
"encoding/json"
"sort"
"strings"
"github.com/go-openapi/jsonpointer"
)
// NewResponsesWithCapacity builds a responses object of the given capacity.
func NewResponsesWithCapacity(cap int) *Responses {
if cap == 0 {
return &Responses{m: make(map[string]*ResponseRef)}
}
return &Responses{m: make(map[string]*ResponseRef, cap)}
}
// Value returns the responses for key or nil
func (responses *Responses) Value(key string) *ResponseRef {
if responses.Len() == 0 {
return nil
}
return responses.m[key]
}
// Set adds or replaces key 'key' of 'responses' with 'value'.
// Note: 'responses' MUST be non-nil
func (responses *Responses) Set(key string, value *ResponseRef) {
if responses.m == nil {
responses.m = make(map[string]*ResponseRef)
}
responses.m[key] = value
}
// Len returns the amount of keys in responses excluding responses.Extensions.
func (responses *Responses) Len() int {
if responses == nil || responses.m == nil {
return 0
}
return len(responses.m)
}
// Delete removes the entry associated with key 'key' from 'responses'.
func (responses *Responses) Delete(key string) {
if responses != nil && responses.m != nil {
delete(responses.m, key)
}
}
// Map returns responses as a 'map'.
// Note: iteration on Go maps is not ordered.
func (responses *Responses) Map() (m map[string]*ResponseRef) {
if responses == nil || len(responses.m) == 0 {
return make(map[string]*ResponseRef)
}
m = make(map[string]*ResponseRef, len(responses.m))
for k, v := range responses.m {
m[k] = v
}
return
}
var _ jsonpointer.JSONPointable = (*Responses)(nil)
// JSONLookup implements https://github.com/go-openapi/jsonpointer#JSONPointable
func (responses Responses) JSONLookup(token string) (interface{}, error) {
if v := responses.Value(token); v == nil {
vv, _, err := jsonpointer.GetForToken(responses.Extensions, token)
return vv, err
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
var vv *Response = v.Value
return vv, nil
}
}
// MarshalJSON returns the JSON encoding of Responses.
func (responses *Responses) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{}, responses.Len()+len(responses.Extensions))
for k, v := range responses.Extensions {
m[k] = v
}
for k, v := range responses.Map() {
m[k] = v
}
return json.Marshal(m)
}
// UnmarshalJSON sets Responses to a copy of data.
func (responses *Responses) UnmarshalJSON(data []byte) (err error) {
var m map[string]interface{}
if err = json.Unmarshal(data, &m); err != nil {
return
}
ks := make([]string, 0, len(m))
for k := range m {
ks = append(ks, k)
}
sort.Strings(ks)
x := Responses{
Extensions: make(map[string]interface{}),
m: make(map[string]*ResponseRef, len(m)),
}
for _, k := range ks {
v := m[k]
if strings.HasPrefix(k, "x-") {
x.Extensions[k] = v
continue
}
var data []byte
if data, err = json.Marshal(v); err != nil {
return
}
var vv ResponseRef
if err = vv.UnmarshalJSON(data); err != nil {
return
}
x.m[k] = &vv
}
*responses = x
return
}
// NewCallbackWithCapacity builds a callback object of the given capacity.
func NewCallbackWithCapacity(cap int) *Callback {
if cap == 0 {
return &Callback{m: make(map[string]*PathItem)}
}
return &Callback{m: make(map[string]*PathItem, cap)}
}
// Value returns the callback for key or nil
func (callback *Callback) Value(key string) *PathItem {
if callback.Len() == 0 {
return nil
}
return callback.m[key]
}
// Set adds or replaces key 'key' of 'callback' with 'value'.
// Note: 'callback' MUST be non-nil
func (callback *Callback) Set(key string, value *PathItem) {
if callback.m == nil {
callback.m = make(map[string]*PathItem)
}
callback.m[key] = value
}
// Len returns the amount of keys in callback excluding callback.Extensions.
func (callback *Callback) Len() int {
if callback == nil || callback.m == nil {
return 0
}
return len(callback.m)
}
// Delete removes the entry associated with key 'key' from 'callback'.
func (callback *Callback) Delete(key string) {
if callback != nil && callback.m != nil {
delete(callback.m, key)
}
}
// Map returns callback as a 'map'.
// Note: iteration on Go maps is not ordered.
func (callback *Callback) Map() (m map[string]*PathItem) {
if callback == nil || len(callback.m) == 0 {
return make(map[string]*PathItem)
}
m = make(map[string]*PathItem, len(callback.m))
for k, v := range callback.m {
m[k] = v
}
return
}
var _ jsonpointer.JSONPointable = (*Callback)(nil)
// JSONLookup implements https://github.com/go-openapi/jsonpointer#JSONPointable
func (callback Callback) JSONLookup(token string) (interface{}, error) {
if v := callback.Value(token); v == nil {
vv, _, err := jsonpointer.GetForToken(callback.Extensions, token)
return vv, err
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
var vv *PathItem = v
return vv, nil
}
}
// MarshalJSON returns the JSON encoding of Callback.
func (callback *Callback) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{}, callback.Len()+len(callback.Extensions))
for k, v := range callback.Extensions {
m[k] = v
}
for k, v := range callback.Map() {
m[k] = v
}
return json.Marshal(m)
}
// UnmarshalJSON sets Callback to a copy of data.
func (callback *Callback) UnmarshalJSON(data []byte) (err error) {
var m map[string]interface{}
if err = json.Unmarshal(data, &m); err != nil {
return
}
ks := make([]string, 0, len(m))
for k := range m {
ks = append(ks, k)
}
sort.Strings(ks)
x := Callback{
Extensions: make(map[string]interface{}),
m: make(map[string]*PathItem, len(m)),
}
for _, k := range ks {
v := m[k]
if strings.HasPrefix(k, "x-") {
x.Extensions[k] = v
continue
}
var data []byte
if data, err = json.Marshal(v); err != nil {
return
}
var vv PathItem
if err = vv.UnmarshalJSON(data); err != nil {
return
}
x.m[k] = &vv
}
*callback = x
return
}
// NewPathsWithCapacity builds a paths object of the given capacity.
func NewPathsWithCapacity(cap int) *Paths {
if cap == 0 {
return &Paths{m: make(map[string]*PathItem)}
}
return &Paths{m: make(map[string]*PathItem, cap)}
}
// Value returns the paths for key or nil
func (paths *Paths) Value(key string) *PathItem {
if paths.Len() == 0 {
return nil
}
return paths.m[key]
}
// Set adds or replaces key 'key' of 'paths' with 'value'.
// Note: 'paths' MUST be non-nil
func (paths *Paths) Set(key string, value *PathItem) {
if paths.m == nil {
paths.m = make(map[string]*PathItem)
}
paths.m[key] = value
}
// Len returns the amount of keys in paths excluding paths.Extensions.
func (paths *Paths) Len() int {
if paths == nil || paths.m == nil {
return 0
}
return len(paths.m)
}
// Delete removes the entry associated with key 'key' from 'paths'.
func (paths *Paths) Delete(key string) {
if paths != nil && paths.m != nil {
delete(paths.m, key)
}
}
// Map returns paths as a 'map'.
// Note: iteration on Go maps is not ordered.
func (paths *Paths) Map() (m map[string]*PathItem) {
if paths == nil || len(paths.m) == 0 {
return make(map[string]*PathItem)
}
m = make(map[string]*PathItem, len(paths.m))
for k, v := range paths.m {
m[k] = v
}
return
}
var _ jsonpointer.JSONPointable = (*Paths)(nil)
// JSONLookup implements https://github.com/go-openapi/jsonpointer#JSONPointable
func (paths Paths) JSONLookup(token string) (interface{}, error) {
if v := paths.Value(token); v == nil {
vv, _, err := jsonpointer.GetForToken(paths.Extensions, token)
return vv, err
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
var vv *PathItem = v
return vv, nil
}
}
// MarshalJSON returns the JSON encoding of Paths.
func (paths *Paths) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{}, paths.Len()+len(paths.Extensions))
for k, v := range paths.Extensions {
m[k] = v
}
for k, v := range paths.Map() {
m[k] = v
}
return json.Marshal(m)
}
// UnmarshalJSON sets Paths to a copy of data.
func (paths *Paths) UnmarshalJSON(data []byte) (err error) {
var m map[string]interface{}
if err = json.Unmarshal(data, &m); err != nil {
return
}
ks := make([]string, 0, len(m))
for k := range m {
ks = append(ks, k)
}
sort.Strings(ks)
x := Paths{
Extensions: make(map[string]interface{}),
m: make(map[string]*PathItem, len(m)),
}
for _, k := range ks {
v := m[k]
if strings.HasPrefix(k, "x-") {
x.Extensions[k] = v
continue
}
var data []byte
if data, err = json.Marshal(v); err != nil {
return
}
var vv PathItem
if err = vv.UnmarshalJSON(data); err != nil {
return
}
x.m[k] = &vv
}
*paths = x
return
}
kin-openapi-0.124.0/openapi3/maplike_test.go 0000664 0000000 0000000 00000005750 14604223742 0020631 0 ustar 00root root 0000000 0000000 package openapi3
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestMaplikeMethods(t *testing.T) {
t.Parallel()
t.Run("*Responses", func(t *testing.T) {
t.Parallel()
t.Run("nil", func(t *testing.T) {
x := (*Responses)(nil)
require.Equal(t, 0, x.Len())
require.Equal(t, map[string]*ResponseRef{}, x.Map())
require.Equal(t, (*ResponseRef)(nil), x.Value("key"))
require.Panics(t, func() { x.Set("key", &ResponseRef{}) })
require.NotPanics(t, func() { x.Delete("key") })
})
t.Run("nonnil", func(t *testing.T) {
x := &Responses{}
require.Equal(t, 0, x.Len())
require.Equal(t, map[string]*ResponseRef{}, x.Map())
require.Equal(t, (*ResponseRef)(nil), x.Value("key"))
x.Set("key", &ResponseRef{})
require.Equal(t, 1, x.Len())
require.Equal(t, map[string]*ResponseRef{"key": {}}, x.Map())
require.Equal(t, &ResponseRef{}, x.Value("key"))
x.Delete("key")
require.Equal(t, 0, x.Len())
require.Equal(t, map[string]*ResponseRef{}, x.Map())
require.Equal(t, (*ResponseRef)(nil), x.Value("key"))
require.NotPanics(t, func() { x.Delete("key") })
})
})
t.Run("*Callback", func(t *testing.T) {
t.Parallel()
t.Run("nil", func(t *testing.T) {
x := (*Callback)(nil)
require.Equal(t, 0, x.Len())
require.Equal(t, map[string]*PathItem{}, x.Map())
require.Equal(t, (*PathItem)(nil), x.Value("key"))
require.Panics(t, func() { x.Set("key", &PathItem{}) })
require.NotPanics(t, func() { x.Delete("key") })
})
t.Run("nonnil", func(t *testing.T) {
x := &Callback{}
require.Equal(t, 0, x.Len())
require.Equal(t, map[string]*PathItem{}, x.Map())
require.Equal(t, (*PathItem)(nil), x.Value("key"))
x.Set("key", &PathItem{})
require.Equal(t, 1, x.Len())
require.Equal(t, map[string]*PathItem{"key": {}}, x.Map())
require.Equal(t, &PathItem{}, x.Value("key"))
x.Delete("key")
require.Equal(t, 0, x.Len())
require.Equal(t, map[string]*PathItem{}, x.Map())
require.Equal(t, (*PathItem)(nil), x.Value("key"))
require.NotPanics(t, func() { x.Delete("key") })
})
})
t.Run("*Paths", func(t *testing.T) {
t.Parallel()
t.Run("nil", func(t *testing.T) {
x := (*Paths)(nil)
require.Equal(t, 0, x.Len())
require.Equal(t, map[string]*PathItem{}, x.Map())
require.Equal(t, (*PathItem)(nil), x.Value("key"))
require.Panics(t, func() { x.Set("key", &PathItem{}) })
require.NotPanics(t, func() { x.Delete("key") })
})
t.Run("nonnil", func(t *testing.T) {
x := &Paths{}
require.Equal(t, 0, x.Len())
require.Equal(t, map[string]*PathItem{}, x.Map())
require.Equal(t, (*PathItem)(nil), x.Value("key"))
x.Set("key", &PathItem{})
require.Equal(t, 1, x.Len())
require.Equal(t, map[string]*PathItem{"key": {}}, x.Map())
require.Equal(t, &PathItem{}, x.Value("key"))
x.Delete("key")
require.Equal(t, 0, x.Len())
require.Equal(t, map[string]*PathItem{}, x.Map())
require.Equal(t, (*PathItem)(nil), x.Value("key"))
require.NotPanics(t, func() { x.Delete("key") })
})
})
}
kin-openapi-0.124.0/openapi3/marsh.go 0000664 0000000 0000000 00000001311 14604223742 0017247 0 ustar 00root root 0000000 0000000 package openapi3
import (
"encoding/json"
"fmt"
"strings"
"github.com/invopop/yaml"
)
func unmarshalError(jsonUnmarshalErr error) error {
if before, after, found := strings.Cut(jsonUnmarshalErr.Error(), "Bis"); found && before != "" && after != "" {
before = strings.ReplaceAll(before, " Go struct ", " ")
return fmt.Errorf("%s%s", before, strings.ReplaceAll(after, "Bis", ""))
}
return jsonUnmarshalErr
}
func unmarshal(data []byte, v interface{}) error {
// See https://github.com/getkin/kin-openapi/issues/680
if err := json.Unmarshal(data, v); err != nil {
// UnmarshalStrict(data, v) TODO: investigate how ymlv3 handles duplicate map keys
return yaml.Unmarshal(data, v)
}
return nil
}
kin-openapi-0.124.0/openapi3/marsh_test.go 0000664 0000000 0000000 00000002677 14604223742 0020326 0 ustar 00root root 0000000 0000000 package openapi3
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestUnmarshalError(t *testing.T) {
{
spec := []byte(`
openapi: 3.0.1
info:
version: v1
title: Products api
components:
schemas:
someSchema:
type: object
schemaArray:
type: array
minItems: 1
items:
$ref: '#/components/schemas/someSchema'
paths:
/categories:
get:
responses:
'200':
description: ''
content:
application/json:
schema:
allOf:
$ref: '#/components/schemas/schemaArray' # <- Should have been a list
`[1:])
sl := NewLoader()
_, err := sl.LoadFromData(spec)
require.ErrorContains(t, err, `json: cannot unmarshal object into field Schema.allOf of type openapi3.SchemaRefs`)
}
spec := []byte(`
openapi: 3.0.1
info:
version: v1
title: Products api
components:
schemas:
someSchema:
type: object
schemaArray:
type: array
minItems: 1
items:
$ref: '#/components/schemas/someSchema'
paths:
/categories:
get:
responses:
'200':
description: ''
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/schemaArray' # <-
`[1:])
sl := NewLoader()
doc, err := sl.LoadFromData(spec)
require.NoError(t, err)
err = doc.Validate(sl.Context)
require.NoError(t, err)
}
kin-openapi-0.124.0/openapi3/media_type.go 0000664 0000000 0000000 00000011027 14604223742 0020262 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"encoding/json"
"errors"
"fmt"
"sort"
"github.com/go-openapi/jsonpointer"
)
// MediaType is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#media-type-object
type MediaType struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Schema *SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"`
Example interface{} `json:"example,omitempty" yaml:"example,omitempty"`
Examples Examples `json:"examples,omitempty" yaml:"examples,omitempty"`
Encoding map[string]*Encoding `json:"encoding,omitempty" yaml:"encoding,omitempty"`
}
var _ jsonpointer.JSONPointable = (*MediaType)(nil)
func NewMediaType() *MediaType {
return &MediaType{}
}
func (mediaType *MediaType) WithSchema(schema *Schema) *MediaType {
if schema == nil {
mediaType.Schema = nil
} else {
mediaType.Schema = &SchemaRef{Value: schema}
}
return mediaType
}
func (mediaType *MediaType) WithSchemaRef(schema *SchemaRef) *MediaType {
mediaType.Schema = schema
return mediaType
}
func (mediaType *MediaType) WithExample(name string, value interface{}) *MediaType {
example := mediaType.Examples
if example == nil {
example = make(map[string]*ExampleRef)
mediaType.Examples = example
}
example[name] = &ExampleRef{
Value: NewExample(value),
}
return mediaType
}
func (mediaType *MediaType) WithEncoding(name string, enc *Encoding) *MediaType {
encoding := mediaType.Encoding
if encoding == nil {
encoding = make(map[string]*Encoding)
mediaType.Encoding = encoding
}
encoding[name] = enc
return mediaType
}
// MarshalJSON returns the JSON encoding of MediaType.
func (mediaType MediaType) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{}, 4+len(mediaType.Extensions))
for k, v := range mediaType.Extensions {
m[k] = v
}
if x := mediaType.Schema; x != nil {
m["schema"] = x
}
if x := mediaType.Example; x != nil {
m["example"] = x
}
if x := mediaType.Examples; len(x) != 0 {
m["examples"] = x
}
if x := mediaType.Encoding; len(x) != 0 {
m["encoding"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets MediaType to a copy of data.
func (mediaType *MediaType) UnmarshalJSON(data []byte) error {
type MediaTypeBis MediaType
var x MediaTypeBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "schema")
delete(x.Extensions, "example")
delete(x.Extensions, "examples")
delete(x.Extensions, "encoding")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*mediaType = MediaType(x)
return nil
}
// Validate returns an error if MediaType does not comply with the OpenAPI spec.
func (mediaType *MediaType) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if mediaType == nil {
return nil
}
if schema := mediaType.Schema; schema != nil {
if err := schema.Validate(ctx); err != nil {
return err
}
if mediaType.Example != nil && mediaType.Examples != nil {
return errors.New("example and examples are mutually exclusive")
}
if vo := getValidationOptions(ctx); !vo.examplesValidationDisabled {
if example := mediaType.Example; example != nil {
if err := validateExampleValue(ctx, example, schema.Value); err != nil {
return fmt.Errorf("invalid example: %w", err)
}
}
if examples := mediaType.Examples; examples != nil {
names := make([]string, 0, len(examples))
for name := range examples {
names = append(names, name)
}
sort.Strings(names)
for _, k := range names {
v := examples[k]
if err := v.Validate(ctx); err != nil {
return fmt.Errorf("example %s: %w", k, err)
}
if err := validateExampleValue(ctx, v.Value.Value, schema.Value); err != nil {
return fmt.Errorf("example %s: %w", k, err)
}
}
}
}
}
return validateExtensions(ctx, mediaType.Extensions)
}
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (mediaType MediaType) JSONLookup(token string) (interface{}, error) {
switch token {
case "schema":
if mediaType.Schema != nil {
if mediaType.Schema.Ref != "" {
return &Ref{Ref: mediaType.Schema.Ref}, nil
}
return mediaType.Schema.Value, nil
}
case "example":
return mediaType.Example, nil
case "examples":
return mediaType.Examples, nil
case "encoding":
return mediaType.Encoding, nil
}
v, _, err := jsonpointer.GetForToken(mediaType.Extensions, token)
return v, err
}
kin-openapi-0.124.0/openapi3/media_type_test.go 0000664 0000000 0000000 00000002736 14604223742 0021330 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
)
func TestMediaTypeJSON(t *testing.T) {
t.Log("Marshal *openapi3.MediaType to JSON")
data, err := json.Marshal(mediaType())
require.NoError(t, err)
require.NotEmpty(t, data)
t.Log("Unmarshal *openapi3.MediaType from JSON")
docA := &MediaType{}
err = json.Unmarshal(mediaTypeJSON, &docA)
require.NoError(t, err)
require.NotEmpty(t, data)
t.Log("Validate *openapi3.MediaType")
err = docA.Validate(context.Background())
require.NoError(t, err)
t.Log("Ensure representations match")
dataA, err := json.Marshal(docA)
require.NoError(t, err)
require.JSONEq(t, string(data), string(mediaTypeJSON))
require.JSONEq(t, string(data), string(dataA))
}
var mediaTypeJSON = []byte(`
{
"schema": {
"description": "Some schema"
},
"encoding": {
"someEncoding": {
"contentType": "application/xml; charset=utf-8"
}
},
"examples": {
"someExample": {
"value": {
"name": "Some example"
}
}
}
}
`)
func mediaType() *MediaType {
example := map[string]string{"name": "Some example"}
return &MediaType{
Schema: &SchemaRef{
Value: &Schema{
Description: "Some schema",
},
},
Encoding: map[string]*Encoding{
"someEncoding": {
ContentType: "application/xml; charset=utf-8",
},
},
Examples: map[string]*ExampleRef{
"someExample": {
Value: NewExample(example),
},
},
}
}
kin-openapi-0.124.0/openapi3/openapi3.go 0000664 0000000 0000000 00000012033 14604223742 0017656 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/go-openapi/jsonpointer"
)
// T is the root of an OpenAPI v3 document
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#openapi-object
type T struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
OpenAPI string `json:"openapi" yaml:"openapi"` // Required
Components *Components `json:"components,omitempty" yaml:"components,omitempty"`
Info *Info `json:"info" yaml:"info"` // Required
Paths *Paths `json:"paths" yaml:"paths"` // Required
Security SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"`
Servers Servers `json:"servers,omitempty" yaml:"servers,omitempty"`
Tags Tags `json:"tags,omitempty" yaml:"tags,omitempty"`
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
visited visitedComponent
}
var _ jsonpointer.JSONPointable = (*T)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (doc *T) JSONLookup(token string) (interface{}, error) {
switch token {
case "openapi":
return doc.OpenAPI, nil
case "components":
return doc.Components, nil
case "info":
return doc.Info, nil
case "paths":
return doc.Paths, nil
case "security":
return doc.Security, nil
case "servers":
return doc.Servers, nil
case "tags":
return doc.Tags, nil
case "externalDocs":
return doc.ExternalDocs, nil
}
v, _, err := jsonpointer.GetForToken(doc.Extensions, token)
return v, err
}
// MarshalJSON returns the JSON encoding of T.
func (doc *T) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{}, 4+len(doc.Extensions))
for k, v := range doc.Extensions {
m[k] = v
}
m["openapi"] = doc.OpenAPI
if x := doc.Components; x != nil {
m["components"] = x
}
m["info"] = doc.Info
m["paths"] = doc.Paths
if x := doc.Security; len(x) != 0 {
m["security"] = x
}
if x := doc.Servers; len(x) != 0 {
m["servers"] = x
}
if x := doc.Tags; len(x) != 0 {
m["tags"] = x
}
if x := doc.ExternalDocs; x != nil {
m["externalDocs"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets T to a copy of data.
func (doc *T) UnmarshalJSON(data []byte) error {
type TBis T
var x TBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "openapi")
delete(x.Extensions, "components")
delete(x.Extensions, "info")
delete(x.Extensions, "paths")
delete(x.Extensions, "security")
delete(x.Extensions, "servers")
delete(x.Extensions, "tags")
delete(x.Extensions, "externalDocs")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*doc = T(x)
return nil
}
func (doc *T) AddOperation(path string, method string, operation *Operation) {
if doc.Paths == nil {
doc.Paths = NewPaths()
}
pathItem := doc.Paths.Value(path)
if pathItem == nil {
pathItem = &PathItem{}
doc.Paths.Set(path, pathItem)
}
pathItem.SetOperation(method, operation)
}
func (doc *T) AddServer(server *Server) {
doc.Servers = append(doc.Servers, server)
}
func (doc *T) AddServers(servers ...*Server) {
doc.Servers = append(doc.Servers, servers...)
}
// Validate returns an error if T does not comply with the OpenAPI spec.
// Validations Options can be provided to modify the validation behavior.
func (doc *T) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if doc.OpenAPI == "" {
return errors.New("value of openapi must be a non-empty string")
}
var wrap func(error) error
wrap = func(e error) error { return fmt.Errorf("invalid components: %w", e) }
if v := doc.Components; v != nil {
if err := v.Validate(ctx); err != nil {
return wrap(err)
}
}
wrap = func(e error) error { return fmt.Errorf("invalid info: %w", e) }
if v := doc.Info; v != nil {
if err := v.Validate(ctx); err != nil {
return wrap(err)
}
} else {
return wrap(errors.New("must be an object"))
}
wrap = func(e error) error { return fmt.Errorf("invalid paths: %w", e) }
if v := doc.Paths; v != nil {
if err := v.Validate(ctx); err != nil {
return wrap(err)
}
} else {
return wrap(errors.New("must be an object"))
}
wrap = func(e error) error { return fmt.Errorf("invalid security: %w", e) }
if v := doc.Security; v != nil {
if err := v.Validate(ctx); err != nil {
return wrap(err)
}
}
wrap = func(e error) error { return fmt.Errorf("invalid servers: %w", e) }
if v := doc.Servers; v != nil {
if err := v.Validate(ctx); err != nil {
return wrap(err)
}
}
wrap = func(e error) error { return fmt.Errorf("invalid tags: %w", e) }
if v := doc.Tags; v != nil {
if err := v.Validate(ctx); err != nil {
return wrap(err)
}
}
wrap = func(e error) error { return fmt.Errorf("invalid external docs: %w", e) }
if v := doc.ExternalDocs; v != nil {
if err := v.Validate(ctx); err != nil {
return wrap(err)
}
}
return validateExtensions(ctx, doc.Extensions)
}
kin-openapi-0.124.0/openapi3/openapi3_test.go 0000664 0000000 0000000 00000025665 14604223742 0020734 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"encoding/json"
"strings"
"testing"
"github.com/invopop/yaml"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRefsJSON(t *testing.T) {
loader := NewLoader()
t.Log("Marshal *T to JSON")
data, err := json.Marshal(spec())
require.NoError(t, err)
require.NotEmpty(t, data)
t.Log("Unmarshal *T from JSON")
docA := &T{}
err = json.Unmarshal(specJSON, &docA)
require.NoError(t, err)
require.NotEmpty(t, data)
t.Log("Resolve refs in unmarshaled *T")
err = loader.ResolveRefsIn(docA, nil)
require.NoError(t, err)
t.Log("Resolve refs in marshaled *T")
docB, err := loader.LoadFromData(data)
require.NoError(t, err)
require.NotEmpty(t, docB)
t.Log("Validate *T")
err = docA.Validate(loader.Context)
require.NoError(t, err)
err = docB.Validate(loader.Context)
require.NoError(t, err)
t.Log("Ensure representations match")
dataA, err := json.Marshal(docA)
require.NoError(t, err)
dataB, err := json.Marshal(docB)
require.NoError(t, err)
require.JSONEq(t, string(data), string(specJSON))
require.JSONEq(t, string(data), string(dataA))
require.JSONEq(t, string(data), string(dataB))
}
func TestRefsYAML(t *testing.T) {
loader := NewLoader()
t.Log("Marshal *T to YAML")
data, err := yaml.Marshal(spec())
require.NoError(t, err)
require.NotEmpty(t, data)
t.Log("Unmarshal *T from YAML")
docA := &T{}
err = yaml.Unmarshal(specYAML, &docA)
require.NoError(t, err)
require.NotEmpty(t, data)
t.Log("Resolve refs in unmarshaled *T")
err = loader.ResolveRefsIn(docA, nil)
require.NoError(t, err)
t.Log("Resolve refs in marshaled *T")
docB, err := loader.LoadFromData(data)
require.NoError(t, err)
require.NotEmpty(t, docB)
t.Log("Validate *T")
err = docA.Validate(loader.Context)
require.NoError(t, err)
err = docB.Validate(loader.Context)
require.NoError(t, err)
t.Log("Ensure representations match")
dataA, err := yaml.Marshal(docA)
require.NoError(t, err)
dataB, err := yaml.Marshal(docB)
require.NoError(t, err)
eqYAML(t, data, specYAML)
eqYAML(t, data, dataA)
eqYAML(t, data, dataB)
}
func eqYAML(t *testing.T, expected, actual []byte) {
var e, a interface{}
err := yaml.Unmarshal(expected, &e)
require.NoError(t, err)
err = yaml.Unmarshal(actual, &a)
require.NoError(t, err)
require.Equal(t, e, a)
}
var specYAML = []byte(`
openapi: '3.0'
info:
title: MyAPI
version: '0.1'
paths:
"/hello":
parameters:
- "$ref": "#/components/parameters/someParameter"
post:
parameters:
- "$ref": "#/components/parameters/someParameter"
requestBody:
"$ref": "#/components/requestBodies/someRequestBody"
responses:
'200':
"$ref": "#/components/responses/someResponse"
components:
parameters:
someParameter:
description: Some parameter
name: example
in: query
schema:
"$ref": "#/components/schemas/someSchema"
requestBodies:
someRequestBody:
description: Some request body
content: {}
responses:
someResponse:
description: Some response
schemas:
someSchema:
description: Some schema
headers:
otherHeader:
schema: {type: string}
someHeader:
"$ref": "#/components/headers/otherHeader"
examples:
otherExample:
value:
name: Some example
someExample:
"$ref": "#/components/examples/otherExample"
securitySchemes:
otherSecurityScheme:
description: Some security scheme
type: apiKey
in: query
name: token
someSecurityScheme:
"$ref": "#/components/securitySchemes/otherSecurityScheme"
`)
var specJSON = []byte(`
{
"openapi": "3.0",
"info": {
"title": "MyAPI",
"version": "0.1"
},
"paths": {
"/hello": {
"parameters": [
{
"$ref": "#/components/parameters/someParameter"
}
],
"post": {
"parameters": [
{
"$ref": "#/components/parameters/someParameter"
}
],
"requestBody": {
"$ref": "#/components/requestBodies/someRequestBody"
},
"responses": {
"200": {
"$ref": "#/components/responses/someResponse"
}
}
}
}
},
"components": {
"parameters": {
"someParameter": {
"description": "Some parameter",
"name": "example",
"in": "query",
"schema": {
"$ref": "#/components/schemas/someSchema"
}
}
},
"requestBodies": {
"someRequestBody": {
"description": "Some request body",
"content": {}
}
},
"responses": {
"someResponse": {
"description": "Some response"
}
},
"schemas": {
"someSchema": {
"description": "Some schema"
}
},
"headers": {
"otherHeader": {
"schema": {
"type": "string"
}
},
"someHeader": {
"$ref": "#/components/headers/otherHeader"
}
},
"examples": {
"otherExample": {
"value": {
"name": "Some example"
}
},
"someExample": {
"$ref": "#/components/examples/otherExample"
}
},
"securitySchemes": {
"otherSecurityScheme": {
"description": "Some security scheme",
"type": "apiKey",
"in": "query",
"name": "token"
},
"someSecurityScheme": {
"$ref": "#/components/securitySchemes/otherSecurityScheme"
}
}
}
}
`)
func spec() *T {
parameter := &Parameter{
Description: "Some parameter",
Name: "example",
In: "query",
Schema: &SchemaRef{
Ref: "#/components/schemas/someSchema",
},
}
requestBody := &RequestBody{
Description: "Some request body",
Content: NewContent(),
}
responseDescription := "Some response"
response := &Response{
Description: &responseDescription,
}
schema := &Schema{
Description: "Some schema",
}
example := map[string]string{"name": "Some example"}
return &T{
OpenAPI: "3.0",
Info: &Info{
Title: "MyAPI",
Version: "0.1",
},
Paths: NewPaths(
WithPath("/hello", &PathItem{
Post: &Operation{
Parameters: Parameters{
{
Ref: "#/components/parameters/someParameter",
Value: parameter,
},
},
RequestBody: &RequestBodyRef{
Ref: "#/components/requestBodies/someRequestBody",
Value: requestBody,
},
Responses: NewResponses(
WithStatus(200, &ResponseRef{
Ref: "#/components/responses/someResponse",
Value: response,
}),
),
},
Parameters: Parameters{
{
Ref: "#/components/parameters/someParameter",
Value: parameter,
},
},
}),
),
Components: &Components{
Parameters: ParametersMap{
"someParameter": {Value: parameter},
},
RequestBodies: RequestBodies{
"someRequestBody": {Value: requestBody},
},
Responses: ResponseBodies{
"someResponse": {Value: response},
},
Schemas: Schemas{
"someSchema": {Value: schema},
},
Headers: Headers{
"someHeader": {Ref: "#/components/headers/otherHeader"},
"otherHeader": {Value: &Header{Parameter{Schema: &SchemaRef{Value: NewStringSchema()}}}},
},
Examples: Examples{
"someExample": {Ref: "#/components/examples/otherExample"},
"otherExample": {Value: NewExample(example)},
},
SecuritySchemes: SecuritySchemes{
"someSecurityScheme": {Ref: "#/components/securitySchemes/otherSecurityScheme"},
"otherSecurityScheme": {
Value: &SecurityScheme{
Description: "Some security scheme",
Type: "apiKey",
In: "query",
Name: "token",
},
},
},
},
}
}
func TestValidation(t *testing.T) {
version := `
openapi: 3.0.2
`
info := `
info:
title: "Hello World REST APIs"
version: "1.0"
`
paths := `
paths:
"/api/v2/greetings.json":
get:
operationId: listGreetings
responses:
200:
description: "List different greetings"
"/api/v2/greetings/{id}.json":
parameters:
- name: id
in: path
required: true
schema:
type: string
example: "greeting"
get:
operationId: showGreeting
responses:
200:
description: "Get a single greeting object"
`
externalDocs := `
externalDocs:
url: https://root-ext-docs.com
`
tags := `
tags:
- name: "pet"
externalDocs:
url: https://tags-ext-docs.com
`
spec := version + info + paths + externalDocs + tags + `
components:
schemas:
GreetingObject:
properties:
id:
type: string
type:
type: string
default: "greeting"
attributes:
properties:
description:
type: string
`
tests := []struct {
name string
spec string
expectedErr string
}{
{
name: "no errors",
spec: spec,
},
{
name: "version is missing",
spec: strings.Replace(spec, version, "", 1),
expectedErr: "value of openapi must be a non-empty string",
},
{
name: "version is empty string",
spec: strings.Replace(spec, version, "openapi: ''", 1),
expectedErr: "value of openapi must be a non-empty string",
},
{
name: "info section is missing",
spec: strings.Replace(spec, info, ``, 1),
expectedErr: "invalid info: must be an object",
},
{
name: "paths section is missing",
spec: strings.Replace(spec, paths, ``, 1),
expectedErr: "invalid paths: must be an object",
},
{
name: "externalDocs section is invalid",
spec: strings.Replace(spec, externalDocs,
strings.ReplaceAll(externalDocs, "url: https://root-ext-docs.com", "url: ''"), 1),
expectedErr: "invalid external docs: url is required",
},
{
name: "tags section is invalid",
spec: strings.Replace(spec, tags,
strings.ReplaceAll(tags, "url: https://tags-ext-docs.com", "url: ''"), 1),
expectedErr: "invalid tags: invalid external docs: url is required",
},
}
for i := range tests {
tt := tests[i]
t.Run(tt.name, func(t *testing.T) {
doc := &T{}
err := yaml.Unmarshal([]byte(tt.spec), &doc)
require.NoError(t, err)
err = doc.Validate(context.Background())
if tt.expectedErr != "" {
require.EqualError(t, err, tt.expectedErr)
} else {
require.NoError(t, err)
}
})
}
}
func TestAddRemoveServer(t *testing.T) {
testServerLines := []*Server{{URL: "test0.com"}, {URL: "test1.com"}, {URL: "test3.com"}}
doc3 := &T{
OpenAPI: "3.0.3",
Components: &Components{},
}
assert.Empty(t, doc3.Servers)
doc3.AddServer(&Server{URL: "testserver1.com"})
assert.NotEmpty(t, doc3.Servers)
assert.Len(t, doc3.Servers, 1)
doc3.Servers = Servers{}
assert.Empty(t, doc3.Servers)
doc3.AddServers(testServerLines[0], testServerLines[1], testServerLines[2])
assert.NotEmpty(t, doc3.Servers)
assert.Len(t, doc3.Servers, 3)
doc3.Servers = Servers{}
doc3.AddServers(testServerLines...)
assert.NotEmpty(t, doc3.Servers)
assert.Len(t, doc3.Servers, 3)
doc3.Servers = Servers{}
}
kin-openapi-0.124.0/openapi3/operation.go 0000664 0000000 0000000 00000013456 14604223742 0020152 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"encoding/json"
"errors"
"fmt"
"strconv"
"github.com/go-openapi/jsonpointer"
)
// Operation represents "operation" specified by" OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#operation-object
type Operation struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
// Optional tags for documentation.
Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"`
// Optional short summary.
Summary string `json:"summary,omitempty" yaml:"summary,omitempty"`
// Optional description. Should use CommonMark syntax.
Description string `json:"description,omitempty" yaml:"description,omitempty"`
// Optional operation ID.
OperationID string `json:"operationId,omitempty" yaml:"operationId,omitempty"`
// Optional parameters.
Parameters Parameters `json:"parameters,omitempty" yaml:"parameters,omitempty"`
// Optional body parameter.
RequestBody *RequestBodyRef `json:"requestBody,omitempty" yaml:"requestBody,omitempty"`
// Responses.
Responses *Responses `json:"responses" yaml:"responses"` // Required
// Optional callbacks
Callbacks Callbacks `json:"callbacks,omitempty" yaml:"callbacks,omitempty"`
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
// Optional security requirements that overrides top-level security.
Security *SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"`
// Optional servers that overrides top-level servers.
Servers *Servers `json:"servers,omitempty" yaml:"servers,omitempty"`
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
}
var _ jsonpointer.JSONPointable = (*Operation)(nil)
func NewOperation() *Operation {
return &Operation{}
}
// MarshalJSON returns the JSON encoding of Operation.
func (operation Operation) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{}, 12+len(operation.Extensions))
for k, v := range operation.Extensions {
m[k] = v
}
if x := operation.Tags; len(x) != 0 {
m["tags"] = x
}
if x := operation.Summary; x != "" {
m["summary"] = x
}
if x := operation.Description; x != "" {
m["description"] = x
}
if x := operation.OperationID; x != "" {
m["operationId"] = x
}
if x := operation.Parameters; len(x) != 0 {
m["parameters"] = x
}
if x := operation.RequestBody; x != nil {
m["requestBody"] = x
}
m["responses"] = operation.Responses
if x := operation.Callbacks; len(x) != 0 {
m["callbacks"] = x
}
if x := operation.Deprecated; x {
m["deprecated"] = x
}
if x := operation.Security; x != nil {
m["security"] = x
}
if x := operation.Servers; x != nil {
m["servers"] = x
}
if x := operation.ExternalDocs; x != nil {
m["externalDocs"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets Operation to a copy of data.
func (operation *Operation) UnmarshalJSON(data []byte) error {
type OperationBis Operation
var x OperationBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "tags")
delete(x.Extensions, "summary")
delete(x.Extensions, "description")
delete(x.Extensions, "operationId")
delete(x.Extensions, "parameters")
delete(x.Extensions, "requestBody")
delete(x.Extensions, "responses")
delete(x.Extensions, "callbacks")
delete(x.Extensions, "deprecated")
delete(x.Extensions, "security")
delete(x.Extensions, "servers")
delete(x.Extensions, "externalDocs")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*operation = Operation(x)
return nil
}
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (operation Operation) JSONLookup(token string) (interface{}, error) {
switch token {
case "requestBody":
if operation.RequestBody != nil {
if operation.RequestBody.Ref != "" {
return &Ref{Ref: operation.RequestBody.Ref}, nil
}
return operation.RequestBody.Value, nil
}
case "tags":
return operation.Tags, nil
case "summary":
return operation.Summary, nil
case "description":
return operation.Description, nil
case "operationID":
return operation.OperationID, nil
case "parameters":
return operation.Parameters, nil
case "responses":
return operation.Responses, nil
case "callbacks":
return operation.Callbacks, nil
case "deprecated":
return operation.Deprecated, nil
case "security":
return operation.Security, nil
case "servers":
return operation.Servers, nil
case "externalDocs":
return operation.ExternalDocs, nil
}
v, _, err := jsonpointer.GetForToken(operation.Extensions, token)
return v, err
}
func (operation *Operation) AddParameter(p *Parameter) {
operation.Parameters = append(operation.Parameters, &ParameterRef{Value: p})
}
func (operation *Operation) AddResponse(status int, response *Response) {
code := "default"
if 0 < status && status < 1000 {
code = strconv.FormatInt(int64(status), 10)
}
if operation.Responses == nil {
operation.Responses = NewResponses()
}
operation.Responses.Set(code, &ResponseRef{Value: response})
}
// Validate returns an error if Operation does not comply with the OpenAPI spec.
func (operation *Operation) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if v := operation.Parameters; v != nil {
if err := v.Validate(ctx); err != nil {
return err
}
}
if v := operation.RequestBody; v != nil {
if err := v.Validate(ctx); err != nil {
return err
}
}
if v := operation.Responses; v != nil {
if err := v.Validate(ctx); err != nil {
return err
}
} else {
return errors.New("value of responses must be an object")
}
if v := operation.ExternalDocs; v != nil {
if err := v.Validate(ctx); err != nil {
return fmt.Errorf("invalid external docs: %w", err)
}
}
return validateExtensions(ctx, operation.Extensions)
}
kin-openapi-0.124.0/openapi3/operation_test.go 0000664 0000000 0000000 00000003477 14604223742 0021213 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/require"
)
func initOperation() *Operation {
operation := NewOperation()
operation.Description = "Some description"
operation.Summary = "Some summary"
operation.Tags = []string{"tag1", "tag2"}
return operation
}
func TestAddParameter(t *testing.T) {
operation := initOperation()
operation.AddParameter(NewQueryParameter("param1"))
operation.AddParameter(NewCookieParameter("param2"))
require.Equal(t, "param1", operation.Parameters.GetByInAndName("query", "param1").Name)
require.Equal(t, "param2", operation.Parameters.GetByInAndName("cookie", "param2").Name)
}
func TestAddResponse(t *testing.T) {
operation := initOperation()
operation.AddResponse(200, NewResponse())
operation.AddResponse(400, NewResponse())
require.NotNil(t, "status 200", operation.Responses.Status(200).Value)
require.NotNil(t, "status 400", operation.Responses.Status(400).Value)
}
func operationWithoutResponses() *Operation {
operation := initOperation()
return operation
}
func operationWithResponses() *Operation {
operation := initOperation()
operation.AddResponse(200, NewResponse().WithDescription("some response"))
return operation
}
func TestOperationValidation(t *testing.T) {
tests := []struct {
name string
input *Operation
expectedError error
}{
{
"when no Responses object is provided",
operationWithoutResponses(),
errors.New("value of responses must be an object"),
},
{
"when a Responses object is provided",
operationWithResponses(),
nil,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
c := context.Background()
validationErr := test.input.Validate(c)
require.Equal(t, test.expectedError, validationErr, "expected errors (or lack of) to match")
})
}
}
kin-openapi-0.124.0/openapi3/parameter.go 0000664 0000000 0000000 00000027735 14604223742 0020137 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"encoding/json"
"errors"
"fmt"
"sort"
"strconv"
"github.com/go-openapi/jsonpointer"
)
// Parameters is specified by OpenAPI/Swagger 3.0 standard.
type Parameters []*ParameterRef
var _ jsonpointer.JSONPointable = (*Parameters)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (p Parameters) JSONLookup(token string) (interface{}, error) {
index, err := strconv.Atoi(token)
if err != nil {
return nil, err
}
if index < 0 || index >= len(p) {
return nil, fmt.Errorf("index %d out of bounds of array of length %d", index, len(p))
}
ref := p[index]
if ref != nil && ref.Ref != "" {
return &Ref{Ref: ref.Ref}, nil
}
return ref.Value, nil
}
func NewParameters() Parameters {
return make(Parameters, 0, 4)
}
func (parameters Parameters) GetByInAndName(in string, name string) *Parameter {
for _, item := range parameters {
if v := item.Value; v != nil {
if v.Name == name && v.In == in {
return v
}
}
}
return nil
}
// Validate returns an error if Parameters does not comply with the OpenAPI spec.
func (parameters Parameters) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
dupes := make(map[string]struct{})
for _, parameterRef := range parameters {
if v := parameterRef.Value; v != nil {
key := v.In + ":" + v.Name
if _, ok := dupes[key]; ok {
return fmt.Errorf("more than one %q parameter has name %q", v.In, v.Name)
}
dupes[key] = struct{}{}
}
if err := parameterRef.Validate(ctx); err != nil {
return err
}
}
return nil
}
// Parameter is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#parameter-object
type Parameter struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
In string `json:"in,omitempty" yaml:"in,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Style string `json:"style,omitempty" yaml:"style,omitempty"`
Explode *bool `json:"explode,omitempty" yaml:"explode,omitempty"`
AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"`
AllowReserved bool `json:"allowReserved,omitempty" yaml:"allowReserved,omitempty"`
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
Required bool `json:"required,omitempty" yaml:"required,omitempty"`
Schema *SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"`
Example interface{} `json:"example,omitempty" yaml:"example,omitempty"`
Examples Examples `json:"examples,omitempty" yaml:"examples,omitempty"`
Content Content `json:"content,omitempty" yaml:"content,omitempty"`
}
var _ jsonpointer.JSONPointable = (*Parameter)(nil)
const (
ParameterInPath = "path"
ParameterInQuery = "query"
ParameterInHeader = "header"
ParameterInCookie = "cookie"
)
func NewPathParameter(name string) *Parameter {
return &Parameter{
Name: name,
In: ParameterInPath,
Required: true,
}
}
func NewQueryParameter(name string) *Parameter {
return &Parameter{
Name: name,
In: ParameterInQuery,
}
}
func NewHeaderParameter(name string) *Parameter {
return &Parameter{
Name: name,
In: ParameterInHeader,
}
}
func NewCookieParameter(name string) *Parameter {
return &Parameter{
Name: name,
In: ParameterInCookie,
}
}
func (parameter *Parameter) WithDescription(value string) *Parameter {
parameter.Description = value
return parameter
}
func (parameter *Parameter) WithRequired(value bool) *Parameter {
parameter.Required = value
return parameter
}
func (parameter *Parameter) WithSchema(value *Schema) *Parameter {
if value == nil {
parameter.Schema = nil
} else {
parameter.Schema = &SchemaRef{
Value: value,
}
}
return parameter
}
// MarshalJSON returns the JSON encoding of Parameter.
func (parameter Parameter) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{}, 13+len(parameter.Extensions))
for k, v := range parameter.Extensions {
m[k] = v
}
if x := parameter.Name; x != "" {
m["name"] = x
}
if x := parameter.In; x != "" {
m["in"] = x
}
if x := parameter.Description; x != "" {
m["description"] = x
}
if x := parameter.Style; x != "" {
m["style"] = x
}
if x := parameter.Explode; x != nil {
m["explode"] = x
}
if x := parameter.AllowEmptyValue; x {
m["allowEmptyValue"] = x
}
if x := parameter.AllowReserved; x {
m["allowReserved"] = x
}
if x := parameter.Deprecated; x {
m["deprecated"] = x
}
if x := parameter.Required; x {
m["required"] = x
}
if x := parameter.Schema; x != nil {
m["schema"] = x
}
if x := parameter.Example; x != nil {
m["example"] = x
}
if x := parameter.Examples; len(x) != 0 {
m["examples"] = x
}
if x := parameter.Content; len(x) != 0 {
m["content"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets Parameter to a copy of data.
func (parameter *Parameter) UnmarshalJSON(data []byte) error {
type ParameterBis Parameter
var x ParameterBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "name")
delete(x.Extensions, "in")
delete(x.Extensions, "description")
delete(x.Extensions, "style")
delete(x.Extensions, "explode")
delete(x.Extensions, "allowEmptyValue")
delete(x.Extensions, "allowReserved")
delete(x.Extensions, "deprecated")
delete(x.Extensions, "required")
delete(x.Extensions, "schema")
delete(x.Extensions, "example")
delete(x.Extensions, "examples")
delete(x.Extensions, "content")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*parameter = Parameter(x)
return nil
}
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (parameter Parameter) JSONLookup(token string) (interface{}, error) {
switch token {
case "schema":
if parameter.Schema != nil {
if parameter.Schema.Ref != "" {
return &Ref{Ref: parameter.Schema.Ref}, nil
}
return parameter.Schema.Value, nil
}
case "name":
return parameter.Name, nil
case "in":
return parameter.In, nil
case "description":
return parameter.Description, nil
case "style":
return parameter.Style, nil
case "explode":
return parameter.Explode, nil
case "allowEmptyValue":
return parameter.AllowEmptyValue, nil
case "allowReserved":
return parameter.AllowReserved, nil
case "deprecated":
return parameter.Deprecated, nil
case "required":
return parameter.Required, nil
case "example":
return parameter.Example, nil
case "examples":
return parameter.Examples, nil
case "content":
return parameter.Content, nil
}
v, _, err := jsonpointer.GetForToken(parameter.Extensions, token)
return v, err
}
// SerializationMethod returns a parameter's serialization method.
// When a parameter's serialization method is not defined the method returns
// the default serialization method corresponding to a parameter's location.
func (parameter *Parameter) SerializationMethod() (*SerializationMethod, error) {
switch parameter.In {
case ParameterInPath, ParameterInHeader:
style := parameter.Style
if style == "" {
style = SerializationSimple
}
explode := false
if parameter.Explode != nil {
explode = *parameter.Explode
}
return &SerializationMethod{Style: style, Explode: explode}, nil
case ParameterInQuery, ParameterInCookie:
style := parameter.Style
if style == "" {
style = SerializationForm
}
explode := true
if parameter.Explode != nil {
explode = *parameter.Explode
}
return &SerializationMethod{Style: style, Explode: explode}, nil
default:
return nil, fmt.Errorf("unexpected parameter's 'in': %q", parameter.In)
}
}
// Validate returns an error if Parameter does not comply with the OpenAPI spec.
func (parameter *Parameter) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if parameter.Name == "" {
return errors.New("parameter name can't be blank")
}
in := parameter.In
switch in {
case
ParameterInPath,
ParameterInQuery,
ParameterInHeader,
ParameterInCookie:
default:
return fmt.Errorf("parameter can't have 'in' value %q", parameter.In)
}
if in == ParameterInPath && !parameter.Required {
return fmt.Errorf("path parameter %q must be required", parameter.Name)
}
// Validate a parameter's serialization method.
sm, err := parameter.SerializationMethod()
if err != nil {
return err
}
var smSupported bool
switch {
case parameter.In == ParameterInPath && sm.Style == SerializationSimple && !sm.Explode,
parameter.In == ParameterInPath && sm.Style == SerializationSimple && sm.Explode,
parameter.In == ParameterInPath && sm.Style == SerializationLabel && !sm.Explode,
parameter.In == ParameterInPath && sm.Style == SerializationLabel && sm.Explode,
parameter.In == ParameterInPath && sm.Style == SerializationMatrix && !sm.Explode,
parameter.In == ParameterInPath && sm.Style == SerializationMatrix && sm.Explode,
parameter.In == ParameterInQuery && sm.Style == SerializationForm && sm.Explode,
parameter.In == ParameterInQuery && sm.Style == SerializationForm && !sm.Explode,
parameter.In == ParameterInQuery && sm.Style == SerializationSpaceDelimited && sm.Explode,
parameter.In == ParameterInQuery && sm.Style == SerializationSpaceDelimited && !sm.Explode,
parameter.In == ParameterInQuery && sm.Style == SerializationPipeDelimited && sm.Explode,
parameter.In == ParameterInQuery && sm.Style == SerializationPipeDelimited && !sm.Explode,
parameter.In == ParameterInQuery && sm.Style == SerializationDeepObject && sm.Explode,
parameter.In == ParameterInHeader && sm.Style == SerializationSimple && !sm.Explode,
parameter.In == ParameterInHeader && sm.Style == SerializationSimple && sm.Explode,
parameter.In == ParameterInCookie && sm.Style == SerializationForm && !sm.Explode,
parameter.In == ParameterInCookie && sm.Style == SerializationForm && sm.Explode:
smSupported = true
}
if !smSupported {
e := fmt.Errorf("serialization method with style=%q and explode=%v is not supported by a %s parameter", sm.Style, sm.Explode, in)
return fmt.Errorf("parameter %q schema is invalid: %w", parameter.Name, e)
}
if (parameter.Schema == nil) == (len(parameter.Content) == 0) {
e := errors.New("parameter must contain exactly one of content and schema")
return fmt.Errorf("parameter %q schema is invalid: %w", parameter.Name, e)
}
if content := parameter.Content; content != nil {
e := errors.New("parameter content must only contain one entry")
if len(content) > 1 {
return fmt.Errorf("parameter %q content is invalid: %w", parameter.Name, e)
}
if err := content.Validate(ctx); err != nil {
return fmt.Errorf("parameter %q content is invalid: %w", parameter.Name, err)
}
}
if schema := parameter.Schema; schema != nil {
if err := schema.Validate(ctx); err != nil {
return fmt.Errorf("parameter %q schema is invalid: %w", parameter.Name, err)
}
if parameter.Example != nil && parameter.Examples != nil {
return fmt.Errorf("parameter %q example and examples are mutually exclusive", parameter.Name)
}
if vo := getValidationOptions(ctx); vo.examplesValidationDisabled {
return nil
}
if example := parameter.Example; example != nil {
if err := validateExampleValue(ctx, example, schema.Value); err != nil {
return fmt.Errorf("invalid example: %w", err)
}
} else if examples := parameter.Examples; examples != nil {
names := make([]string, 0, len(examples))
for name := range examples {
names = append(names, name)
}
sort.Strings(names)
for _, k := range names {
v := examples[k]
if err := v.Validate(ctx); err != nil {
return fmt.Errorf("%s: %w", k, err)
}
if err := validateExampleValue(ctx, v.Value.Value, schema.Value); err != nil {
return fmt.Errorf("%s: %w", k, err)
}
}
}
}
return validateExtensions(ctx, parameter.Extensions)
}
kin-openapi-0.124.0/openapi3/parameter_issue223_test.go 0000664 0000000 0000000 00000005116 14604223742 0022622 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"testing"
"github.com/stretchr/testify/require"
)
func TestPathParametersMatchPath(t *testing.T) {
spec := []byte(`
openapi: "3.0.0"
info:
version: 1.0.0
title: Swagger Petstore
license:
name: MIT
servers:
- url: http://petstore.swagger.io/v1
paths:
/pets:
get:
summary: List all pets
operationId: listPets
tags:
- pets
responses:
'200':
description: A paged array of pets
headers:
x-next:
description: A link to the next page of responses
schema:
type: string
content:
application/json:
schema:
$ref: "#/components/schemas/Pets"
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
post:
summary: Create a pet
operationId: createPets
tags:
- pets
responses:
'201':
description: Null response
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/pets/{petId}:
get:
summary: Info for a specific pet
operationId: showPetById
tags:
- pets
# <------------------ no parameters
responses:
'200':
description: Expected response to a valid request
content:
application/json:
schema:
$ref: "#/components/schemas/Pet"
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
components:
schemas:
Pet:
type: object
required:
- id
- name
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
Pets:
type: array
items:
$ref: "#/components/schemas/Pet"
Error:
type: object
required:
- code
- message
properties:
code:
type: integer
format: int32
message:
type: string
`[1:])
loader := NewLoader()
doc, err := loader.LoadFromData(spec)
require.NoError(t, err)
err = doc.Validate(context.Background())
require.EqualError(t, err, `invalid paths: operation GET /pets/{petId} must define exactly all path parameters (missing: [petId])`)
}
kin-openapi-0.124.0/openapi3/parameter_issue834_test.go 0000664 0000000 0000000 00000004706 14604223742 0022636 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"testing"
"github.com/stretchr/testify/require"
)
func TestPathItemParametersAreValidated(t *testing.T) {
spec := []byte(`
openapi: "3.0.0"
info:
version: 1.0.0
title: Swagger Petstore
license:
name: MIT
servers:
- url: http://petstore.swagger.io/v1
paths:
/pets:
parameters:
- in: invalid
name: test
schema:
type: string
get:
summary: List all pets
operationId: listPets
tags:
- pets
responses:
'200':
description: A paged array of pets
`[1:])
loader := NewLoader()
doc, err := loader.LoadFromData(spec)
require.NoError(t, err)
err = doc.Validate(context.Background())
require.EqualError(t, err, `invalid paths: invalid path /pets: parameter can't have 'in' value "invalid"`)
}
func TestParameterMultipleContentEntries(t *testing.T) {
spec := []byte(`
openapi: "3.0.0"
info:
version: 1.0.0
title: Swagger Petstore
license:
name: MIT
servers:
- url: http://petstore.swagger.io/v1
paths:
/pets:
parameters:
- in: query
name: test
content:
application/json:
schema:
type: string
application/xml:
schema:
type: string
get:
summary: List all pets
operationId: listPets
tags:
- pets
responses:
'200':
description: A paged array of pets
`[1:])
loader := NewLoader()
doc, err := loader.LoadFromData(spec)
require.NoError(t, err)
err = doc.Validate(context.Background())
require.EqualError(t, err, `invalid paths: invalid path /pets: parameter "test" content is invalid: parameter content must only contain one entry`)
}
func TestParameterEmptyContent(t *testing.T) {
spec := []byte(`
openapi: "3.0.0"
info:
version: 1.0.0
title: Swagger Petstore
license:
name: MIT
servers:
- url: http://petstore.swagger.io/v1
paths:
/pets:
parameters:
- in: query
name: test
content: {}
get:
summary: List all pets
operationId: listPets
tags:
- pets
responses:
'200':
description: A paged array of pets
`[1:])
loader := NewLoader()
doc, err := loader.LoadFromData(spec)
require.NoError(t, err)
err = doc.Validate(context.Background())
require.EqualError(t, err, `invalid paths: invalid path /pets: parameter "test" schema is invalid: parameter must contain exactly one of content and schema`)
}
kin-openapi-0.124.0/openapi3/path_item.go 0000664 0000000 0000000 00000014647 14604223742 0020127 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"encoding/json"
"fmt"
"net/http"
"sort"
)
// PathItem is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#path-item-object
type PathItem struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
Summary string `json:"summary,omitempty" yaml:"summary,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Connect *Operation `json:"connect,omitempty" yaml:"connect,omitempty"`
Delete *Operation `json:"delete,omitempty" yaml:"delete,omitempty"`
Get *Operation `json:"get,omitempty" yaml:"get,omitempty"`
Head *Operation `json:"head,omitempty" yaml:"head,omitempty"`
Options *Operation `json:"options,omitempty" yaml:"options,omitempty"`
Patch *Operation `json:"patch,omitempty" yaml:"patch,omitempty"`
Post *Operation `json:"post,omitempty" yaml:"post,omitempty"`
Put *Operation `json:"put,omitempty" yaml:"put,omitempty"`
Trace *Operation `json:"trace,omitempty" yaml:"trace,omitempty"`
Servers Servers `json:"servers,omitempty" yaml:"servers,omitempty"`
Parameters Parameters `json:"parameters,omitempty" yaml:"parameters,omitempty"`
}
// MarshalJSON returns the JSON encoding of PathItem.
func (pathItem PathItem) MarshalJSON() ([]byte, error) {
if ref := pathItem.Ref; ref != "" {
return json.Marshal(Ref{Ref: ref})
}
m := make(map[string]interface{}, 13+len(pathItem.Extensions))
for k, v := range pathItem.Extensions {
m[k] = v
}
if x := pathItem.Summary; x != "" {
m["summary"] = x
}
if x := pathItem.Description; x != "" {
m["description"] = x
}
if x := pathItem.Connect; x != nil {
m["connect"] = x
}
if x := pathItem.Delete; x != nil {
m["delete"] = x
}
if x := pathItem.Get; x != nil {
m["get"] = x
}
if x := pathItem.Head; x != nil {
m["head"] = x
}
if x := pathItem.Options; x != nil {
m["options"] = x
}
if x := pathItem.Patch; x != nil {
m["patch"] = x
}
if x := pathItem.Post; x != nil {
m["post"] = x
}
if x := pathItem.Put; x != nil {
m["put"] = x
}
if x := pathItem.Trace; x != nil {
m["trace"] = x
}
if x := pathItem.Servers; len(x) != 0 {
m["servers"] = x
}
if x := pathItem.Parameters; len(x) != 0 {
m["parameters"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets PathItem to a copy of data.
func (pathItem *PathItem) UnmarshalJSON(data []byte) error {
type PathItemBis PathItem
var x PathItemBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "$ref")
delete(x.Extensions, "summary")
delete(x.Extensions, "description")
delete(x.Extensions, "connect")
delete(x.Extensions, "delete")
delete(x.Extensions, "get")
delete(x.Extensions, "head")
delete(x.Extensions, "options")
delete(x.Extensions, "patch")
delete(x.Extensions, "post")
delete(x.Extensions, "put")
delete(x.Extensions, "trace")
delete(x.Extensions, "servers")
delete(x.Extensions, "parameters")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*pathItem = PathItem(x)
return nil
}
func (pathItem *PathItem) Operations() map[string]*Operation {
operations := make(map[string]*Operation)
if v := pathItem.Connect; v != nil {
operations[http.MethodConnect] = v
}
if v := pathItem.Delete; v != nil {
operations[http.MethodDelete] = v
}
if v := pathItem.Get; v != nil {
operations[http.MethodGet] = v
}
if v := pathItem.Head; v != nil {
operations[http.MethodHead] = v
}
if v := pathItem.Options; v != nil {
operations[http.MethodOptions] = v
}
if v := pathItem.Patch; v != nil {
operations[http.MethodPatch] = v
}
if v := pathItem.Post; v != nil {
operations[http.MethodPost] = v
}
if v := pathItem.Put; v != nil {
operations[http.MethodPut] = v
}
if v := pathItem.Trace; v != nil {
operations[http.MethodTrace] = v
}
return operations
}
func (pathItem *PathItem) GetOperation(method string) *Operation {
switch method {
case http.MethodConnect:
return pathItem.Connect
case http.MethodDelete:
return pathItem.Delete
case http.MethodGet:
return pathItem.Get
case http.MethodHead:
return pathItem.Head
case http.MethodOptions:
return pathItem.Options
case http.MethodPatch:
return pathItem.Patch
case http.MethodPost:
return pathItem.Post
case http.MethodPut:
return pathItem.Put
case http.MethodTrace:
return pathItem.Trace
default:
panic(fmt.Errorf("unsupported HTTP method %q", method))
}
}
func (pathItem *PathItem) SetOperation(method string, operation *Operation) {
switch method {
case http.MethodConnect:
pathItem.Connect = operation
case http.MethodDelete:
pathItem.Delete = operation
case http.MethodGet:
pathItem.Get = operation
case http.MethodHead:
pathItem.Head = operation
case http.MethodOptions:
pathItem.Options = operation
case http.MethodPatch:
pathItem.Patch = operation
case http.MethodPost:
pathItem.Post = operation
case http.MethodPut:
pathItem.Put = operation
case http.MethodTrace:
pathItem.Trace = operation
default:
panic(fmt.Errorf("unsupported HTTP method %q", method))
}
}
// Validate returns an error if PathItem does not comply with the OpenAPI spec.
func (pathItem *PathItem) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
operations := pathItem.Operations()
methods := make([]string, 0, len(operations))
for method := range operations {
methods = append(methods, method)
}
sort.Strings(methods)
for _, method := range methods {
operation := operations[method]
if err := operation.Validate(ctx); err != nil {
return fmt.Errorf("invalid operation %s: %v", method, err)
}
}
if v := pathItem.Parameters; v != nil {
if err := v.Validate(ctx); err != nil {
return err
}
}
return validateExtensions(ctx, pathItem.Extensions)
}
// isEmpty's introduced in 546590b1
func (pathItem *PathItem) isEmpty() bool {
// NOTE: ignores pathItem.Extensions
// NOTE: ignores pathItem.Ref
return pathItem.Summary == "" &&
pathItem.Description == "" &&
pathItem.Connect == nil &&
pathItem.Delete == nil &&
pathItem.Get == nil &&
pathItem.Head == nil &&
pathItem.Options == nil &&
pathItem.Patch == nil &&
pathItem.Post == nil &&
pathItem.Put == nil &&
pathItem.Trace == nil &&
len(pathItem.Servers) == 0 &&
len(pathItem.Parameters) == 0
}
kin-openapi-0.124.0/openapi3/paths.go 0000664 0000000 0000000 00000016315 14604223742 0017266 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"fmt"
"sort"
"strings"
)
// Paths is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#paths-object
type Paths struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
m map[string]*PathItem
}
// NewPaths builds a paths object with path items in insertion order.
func NewPaths(opts ...NewPathsOption) *Paths {
paths := NewPathsWithCapacity(len(opts))
for _, opt := range opts {
opt(paths)
}
return paths
}
// NewPathsOption describes options to NewPaths func
type NewPathsOption func(*Paths)
// WithPath adds a named path item
func WithPath(path string, pathItem *PathItem) NewPathsOption {
return func(paths *Paths) {
if p := pathItem; p != nil && path != "" {
paths.Set(path, p)
}
}
}
// Validate returns an error if Paths does not comply with the OpenAPI spec.
func (paths *Paths) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
normalizedPaths := make(map[string]string, paths.Len())
keys := make([]string, 0, paths.Len())
for key := range paths.Map() {
keys = append(keys, key)
}
sort.Strings(keys)
for _, path := range keys {
pathItem := paths.Value(path)
if path == "" || path[0] != '/' {
return fmt.Errorf("path %q does not start with a forward slash (/)", path)
}
if pathItem == nil {
pathItem = &PathItem{}
paths.Set(path, pathItem)
}
normalizedPath, _, varsInPath := normalizeTemplatedPath(path)
if oldPath, ok := normalizedPaths[normalizedPath]; ok {
return fmt.Errorf("conflicting paths %q and %q", path, oldPath)
}
normalizedPaths[path] = path
var commonParams []string
for _, parameterRef := range pathItem.Parameters {
if parameterRef != nil {
if parameter := parameterRef.Value; parameter != nil && parameter.In == ParameterInPath {
commonParams = append(commonParams, parameter.Name)
}
}
}
operations := pathItem.Operations()
methods := make([]string, 0, len(operations))
for method := range operations {
methods = append(methods, method)
}
sort.Strings(methods)
for _, method := range methods {
operation := operations[method]
var setParams []string
for _, parameterRef := range operation.Parameters {
if parameterRef != nil {
if parameter := parameterRef.Value; parameter != nil && parameter.In == ParameterInPath {
setParams = append(setParams, parameter.Name)
}
}
}
if expected := len(setParams) + len(commonParams); expected != len(varsInPath) {
expected -= len(varsInPath)
if expected < 0 {
expected *= -1
}
missing := make(map[string]struct{}, expected)
definedParams := append(setParams, commonParams...)
for _, name := range definedParams {
if _, ok := varsInPath[name]; !ok {
missing[name] = struct{}{}
}
}
for name := range varsInPath {
got := false
for _, othername := range definedParams {
if othername == name {
got = true
break
}
}
if !got {
missing[name] = struct{}{}
}
}
if len(missing) != 0 {
missings := make([]string, 0, len(missing))
for name := range missing {
missings = append(missings, name)
}
return fmt.Errorf("operation %s %s must define exactly all path parameters (missing: %v)", method, path, missings)
}
}
}
if err := pathItem.Validate(ctx); err != nil {
return fmt.Errorf("invalid path %s: %v", path, err)
}
}
if err := paths.validateUniqueOperationIDs(); err != nil {
return err
}
return validateExtensions(ctx, paths.Extensions)
}
// InMatchingOrder returns paths in the order they are matched against URLs.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#paths-object
// When matching URLs, concrete (non-templated) paths would be matched
// before their templated counterparts.
func (paths *Paths) InMatchingOrder() []string {
// NOTE: sorting by number of variables ASC then by descending lexicographical
// order seems to be a good heuristic.
if paths.Len() == 0 {
return nil
}
vars := make(map[int][]string)
max := 0
for path := range paths.Map() {
count := strings.Count(path, "}")
vars[count] = append(vars[count], path)
if count > max {
max = count
}
}
ordered := make([]string, 0, paths.Len())
for c := 0; c <= max; c++ {
if ps, ok := vars[c]; ok {
sort.Sort(sort.Reverse(sort.StringSlice(ps)))
ordered = append(ordered, ps...)
}
}
return ordered
}
// Find returns a path that matches the key.
//
// The method ignores differences in template variable names (except possible "*" suffix).
//
// For example:
//
// paths := openapi3.Paths {
// "/person/{personName}": &openapi3.PathItem{},
// }
// pathItem := path.Find("/person/{name}")
//
// would return the correct path item.
func (paths *Paths) Find(key string) *PathItem {
// Try directly access the map
pathItem := paths.Value(key)
if pathItem != nil {
return pathItem
}
normalizedPath, expected, _ := normalizeTemplatedPath(key)
for path, pathItem := range paths.Map() {
pathNormalized, got, _ := normalizeTemplatedPath(path)
if got == expected && pathNormalized == normalizedPath {
return pathItem
}
}
return nil
}
func (paths *Paths) validateUniqueOperationIDs() error {
operationIDs := make(map[string]string)
for urlPath, pathItem := range paths.Map() {
if pathItem == nil {
continue
}
for httpMethod, operation := range pathItem.Operations() {
if operation == nil || operation.OperationID == "" {
continue
}
endpoint := httpMethod + " " + urlPath
if endpointDup, ok := operationIDs[operation.OperationID]; ok {
if endpoint > endpointDup { // For make error message a bit more deterministic. May be useful for tests.
endpoint, endpointDup = endpointDup, endpoint
}
return fmt.Errorf("operations %q and %q have the same operation id %q",
endpoint, endpointDup, operation.OperationID)
}
operationIDs[operation.OperationID] = endpoint
}
}
return nil
}
// Support YAML Marshaler interface for gopkg.in/yaml
func (paths *Paths) MarshalYAML() (any, error) {
res := make(map[string]any, len(paths.Extensions)+len(paths.m))
for k, v := range paths.Extensions {
res[k] = v
}
for k, v := range paths.m {
res[k] = v
}
return res, nil
}
func normalizeTemplatedPath(path string) (string, uint, map[string]struct{}) {
if strings.IndexByte(path, '{') < 0 {
return path, 0, nil
}
var buffTpl strings.Builder
buffTpl.Grow(len(path))
var (
cc rune
count uint
isVariable bool
vars = make(map[string]struct{})
buffVar strings.Builder
)
for i, c := range path {
if isVariable {
if c == '}' {
// End path variable
isVariable = false
vars[buffVar.String()] = struct{}{}
buffVar = strings.Builder{}
// First append possible '*' before this character
// The character '}' will be appended
if i > 0 && cc == '*' {
buffTpl.WriteRune(cc)
}
} else {
buffVar.WriteRune(c)
continue
}
} else if c == '{' {
// Begin path variable
isVariable = true
// The character '{' will be appended
count++
}
// Append the character
buffTpl.WriteRune(c)
cc = c
}
return buffTpl.String(), count, vars
}
kin-openapi-0.124.0/openapi3/paths_test.go 0000664 0000000 0000000 00000003314 14604223742 0020320 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"testing"
"github.com/stretchr/testify/require"
)
func TestPathsValidate(t *testing.T) {
tests := []struct {
name string
spec string
wantErr string
}{
{
name: "ok, empty paths",
spec: `
openapi: "3.0.0"
info:
version: 1.0.0
title: Swagger Petstore
license:
name: MIT
paths:
/pets:
`,
},
{
name: "operation ids are not unique, same path",
spec: `
openapi: "3.0.0"
info:
version: 1.0.0
title: Swagger Petstore
license:
name: MIT
paths:
/pets:
post:
operationId: createPet
responses:
201:
description: "entity created"
delete:
operationId: createPet
responses:
204:
description: "entity deleted"
`,
wantErr: `operations "DELETE /pets" and "POST /pets" have the same operation id "createPet"`,
},
{
name: "operation ids are not unique, different paths",
spec: `
openapi: "3.0.0"
info:
version: 1.0.0
title: Swagger Petstore
license:
name: MIT
paths:
/pets:
post:
operationId: createPet
responses:
201:
description: "entity created"
/users:
post:
operationId: createPet
responses:
201:
description: "entity created"
`,
wantErr: `operations "POST /pets" and "POST /users" have the same operation id "createPet"`,
},
}
for i := range tests {
tt := tests[i]
t.Run(tt.name, func(t *testing.T) {
loader := NewLoader()
doc, err := loader.LoadFromData([]byte(tt.spec[1:]))
require.NoError(t, err)
err = doc.Paths.Validate(context.Background())
if tt.wantErr == "" {
require.NoError(t, err)
return
}
require.EqualError(t, err, tt.wantErr)
})
}
}
kin-openapi-0.124.0/openapi3/race_test.go 0000664 0000000 0000000 00000001427 14604223742 0020116 0 ustar 00root root 0000000 0000000 package openapi3_test
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
)
func TestRaceyPatternSchemaValidateHindersIt(t *testing.T) {
schema := openapi3.NewStringSchema().WithPattern("^test|for|race|condition$")
err := schema.Validate(context.Background())
require.NoError(t, err)
visit := func() {
err := schema.VisitJSONString("test")
require.NoError(t, err)
}
go visit()
visit()
}
func TestRaceyPatternSchemaForIssue775(t *testing.T) {
schema := openapi3.NewStringSchema().WithPattern("^test|for|race|condition$")
// err := schema.Validate(context.Background())
// require.NoError(t, err)
visit := func() {
err := schema.VisitJSONString("test")
require.NoError(t, err)
}
go visit()
visit()
}
kin-openapi-0.124.0/openapi3/ref.go 0000664 0000000 0000000 00000000342 14604223742 0016714 0 ustar 00root root 0000000 0000000 package openapi3
// Ref is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#reference-object
type Ref struct {
Ref string `json:"$ref" yaml:"$ref"`
}
kin-openapi-0.124.0/openapi3/refs.go 0000664 0000000 0000000 00000050027 14604223742 0017104 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"encoding/json"
"fmt"
"sort"
"github.com/go-openapi/jsonpointer"
"github.com/perimeterx/marshmallow"
)
// CallbackRef represents either a Callback or a $ref to a Callback.
// When serializing and both fields are set, Ref is preferred over Value.
type CallbackRef struct {
Ref string
Value *Callback
extra []string
}
var _ jsonpointer.JSONPointable = (*CallbackRef)(nil)
func (x *CallbackRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil }
// MarshalYAML returns the YAML encoding of CallbackRef.
func (x CallbackRef) MarshalYAML() (interface{}, error) {
if ref := x.Ref; ref != "" {
return &Ref{Ref: ref}, nil
}
return x.Value, nil
}
// MarshalJSON returns the JSON encoding of CallbackRef.
func (x CallbackRef) MarshalJSON() ([]byte, error) {
if ref := x.Ref; ref != "" {
return json.Marshal(Ref{Ref: ref})
}
return json.Marshal(x.Value)
}
// UnmarshalJSON sets CallbackRef to a copy of data.
func (x *CallbackRef) UnmarshalJSON(data []byte) error {
var refOnly Ref
if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" {
x.Ref = refOnly.Ref
if len(extra) != 0 {
x.extra = make([]string, 0, len(extra))
for key := range extra {
x.extra = append(x.extra, key)
}
sort.Strings(x.extra)
}
return nil
}
return json.Unmarshal(data, &x.Value)
}
// Validate returns an error if CallbackRef does not comply with the OpenAPI spec.
func (x *CallbackRef) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if extra := x.extra; len(extra) != 0 {
extras := make([]string, 0, len(extra))
allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed
for _, ex := range extra {
if allowed != nil {
if _, ok := allowed[ex]; ok {
continue
}
}
extras = append(extras, ex)
}
if len(extras) != 0 {
return fmt.Errorf("extra sibling fields: %+v", extras)
}
}
if v := x.Value; v != nil {
return v.Validate(ctx)
}
return foundUnresolvedRef(x.Ref)
}
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (x *CallbackRef) JSONLookup(token string) (interface{}, error) {
if token == "$ref" {
return x.Ref, nil
}
ptr, _, err := jsonpointer.GetForToken(x.Value, token)
return ptr, err
}
// ExampleRef represents either a Example or a $ref to a Example.
// When serializing and both fields are set, Ref is preferred over Value.
type ExampleRef struct {
Ref string
Value *Example
extra []string
}
var _ jsonpointer.JSONPointable = (*ExampleRef)(nil)
func (x *ExampleRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil }
// MarshalYAML returns the YAML encoding of ExampleRef.
func (x ExampleRef) MarshalYAML() (interface{}, error) {
if ref := x.Ref; ref != "" {
return &Ref{Ref: ref}, nil
}
return x.Value, nil
}
// MarshalJSON returns the JSON encoding of ExampleRef.
func (x ExampleRef) MarshalJSON() ([]byte, error) {
if ref := x.Ref; ref != "" {
return json.Marshal(Ref{Ref: ref})
}
return x.Value.MarshalJSON()
}
// UnmarshalJSON sets ExampleRef to a copy of data.
func (x *ExampleRef) UnmarshalJSON(data []byte) error {
var refOnly Ref
if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" {
x.Ref = refOnly.Ref
if len(extra) != 0 {
x.extra = make([]string, 0, len(extra))
for key := range extra {
x.extra = append(x.extra, key)
}
sort.Strings(x.extra)
}
return nil
}
return json.Unmarshal(data, &x.Value)
}
// Validate returns an error if ExampleRef does not comply with the OpenAPI spec.
func (x *ExampleRef) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if extra := x.extra; len(extra) != 0 {
extras := make([]string, 0, len(extra))
allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed
for _, ex := range extra {
if allowed != nil {
if _, ok := allowed[ex]; ok {
continue
}
}
extras = append(extras, ex)
}
if len(extras) != 0 {
return fmt.Errorf("extra sibling fields: %+v", extras)
}
}
if v := x.Value; v != nil {
return v.Validate(ctx)
}
return foundUnresolvedRef(x.Ref)
}
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (x *ExampleRef) JSONLookup(token string) (interface{}, error) {
if token == "$ref" {
return x.Ref, nil
}
ptr, _, err := jsonpointer.GetForToken(x.Value, token)
return ptr, err
}
// HeaderRef represents either a Header or a $ref to a Header.
// When serializing and both fields are set, Ref is preferred over Value.
type HeaderRef struct {
Ref string
Value *Header
extra []string
}
var _ jsonpointer.JSONPointable = (*HeaderRef)(nil)
func (x *HeaderRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil }
// MarshalYAML returns the YAML encoding of HeaderRef.
func (x HeaderRef) MarshalYAML() (interface{}, error) {
if ref := x.Ref; ref != "" {
return &Ref{Ref: ref}, nil
}
return x.Value, nil
}
// MarshalJSON returns the JSON encoding of HeaderRef.
func (x HeaderRef) MarshalJSON() ([]byte, error) {
if ref := x.Ref; ref != "" {
return json.Marshal(Ref{Ref: ref})
}
return x.Value.MarshalJSON()
}
// UnmarshalJSON sets HeaderRef to a copy of data.
func (x *HeaderRef) UnmarshalJSON(data []byte) error {
var refOnly Ref
if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" {
x.Ref = refOnly.Ref
if len(extra) != 0 {
x.extra = make([]string, 0, len(extra))
for key := range extra {
x.extra = append(x.extra, key)
}
sort.Strings(x.extra)
}
return nil
}
return json.Unmarshal(data, &x.Value)
}
// Validate returns an error if HeaderRef does not comply with the OpenAPI spec.
func (x *HeaderRef) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if extra := x.extra; len(extra) != 0 {
extras := make([]string, 0, len(extra))
allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed
for _, ex := range extra {
if allowed != nil {
if _, ok := allowed[ex]; ok {
continue
}
}
extras = append(extras, ex)
}
if len(extras) != 0 {
return fmt.Errorf("extra sibling fields: %+v", extras)
}
}
if v := x.Value; v != nil {
return v.Validate(ctx)
}
return foundUnresolvedRef(x.Ref)
}
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (x *HeaderRef) JSONLookup(token string) (interface{}, error) {
if token == "$ref" {
return x.Ref, nil
}
ptr, _, err := jsonpointer.GetForToken(x.Value, token)
return ptr, err
}
// LinkRef represents either a Link or a $ref to a Link.
// When serializing and both fields are set, Ref is preferred over Value.
type LinkRef struct {
Ref string
Value *Link
extra []string
}
var _ jsonpointer.JSONPointable = (*LinkRef)(nil)
func (x *LinkRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil }
// MarshalYAML returns the YAML encoding of LinkRef.
func (x LinkRef) MarshalYAML() (interface{}, error) {
if ref := x.Ref; ref != "" {
return &Ref{Ref: ref}, nil
}
return x.Value, nil
}
// MarshalJSON returns the JSON encoding of LinkRef.
func (x LinkRef) MarshalJSON() ([]byte, error) {
if ref := x.Ref; ref != "" {
return json.Marshal(Ref{Ref: ref})
}
return x.Value.MarshalJSON()
}
// UnmarshalJSON sets LinkRef to a copy of data.
func (x *LinkRef) UnmarshalJSON(data []byte) error {
var refOnly Ref
if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" {
x.Ref = refOnly.Ref
if len(extra) != 0 {
x.extra = make([]string, 0, len(extra))
for key := range extra {
x.extra = append(x.extra, key)
}
sort.Strings(x.extra)
}
return nil
}
return json.Unmarshal(data, &x.Value)
}
// Validate returns an error if LinkRef does not comply with the OpenAPI spec.
func (x *LinkRef) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if extra := x.extra; len(extra) != 0 {
extras := make([]string, 0, len(extra))
allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed
for _, ex := range extra {
if allowed != nil {
if _, ok := allowed[ex]; ok {
continue
}
}
extras = append(extras, ex)
}
if len(extras) != 0 {
return fmt.Errorf("extra sibling fields: %+v", extras)
}
}
if v := x.Value; v != nil {
return v.Validate(ctx)
}
return foundUnresolvedRef(x.Ref)
}
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (x *LinkRef) JSONLookup(token string) (interface{}, error) {
if token == "$ref" {
return x.Ref, nil
}
ptr, _, err := jsonpointer.GetForToken(x.Value, token)
return ptr, err
}
// ParameterRef represents either a Parameter or a $ref to a Parameter.
// When serializing and both fields are set, Ref is preferred over Value.
type ParameterRef struct {
Ref string
Value *Parameter
extra []string
}
var _ jsonpointer.JSONPointable = (*ParameterRef)(nil)
func (x *ParameterRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil }
// MarshalYAML returns the YAML encoding of ParameterRef.
func (x ParameterRef) MarshalYAML() (interface{}, error) {
if ref := x.Ref; ref != "" {
return &Ref{Ref: ref}, nil
}
return x.Value, nil
}
// MarshalJSON returns the JSON encoding of ParameterRef.
func (x ParameterRef) MarshalJSON() ([]byte, error) {
if ref := x.Ref; ref != "" {
return json.Marshal(Ref{Ref: ref})
}
return x.Value.MarshalJSON()
}
// UnmarshalJSON sets ParameterRef to a copy of data.
func (x *ParameterRef) UnmarshalJSON(data []byte) error {
var refOnly Ref
if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" {
x.Ref = refOnly.Ref
if len(extra) != 0 {
x.extra = make([]string, 0, len(extra))
for key := range extra {
x.extra = append(x.extra, key)
}
sort.Strings(x.extra)
}
return nil
}
return json.Unmarshal(data, &x.Value)
}
// Validate returns an error if ParameterRef does not comply with the OpenAPI spec.
func (x *ParameterRef) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if extra := x.extra; len(extra) != 0 {
extras := make([]string, 0, len(extra))
allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed
for _, ex := range extra {
if allowed != nil {
if _, ok := allowed[ex]; ok {
continue
}
}
extras = append(extras, ex)
}
if len(extras) != 0 {
return fmt.Errorf("extra sibling fields: %+v", extras)
}
}
if v := x.Value; v != nil {
return v.Validate(ctx)
}
return foundUnresolvedRef(x.Ref)
}
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (x *ParameterRef) JSONLookup(token string) (interface{}, error) {
if token == "$ref" {
return x.Ref, nil
}
ptr, _, err := jsonpointer.GetForToken(x.Value, token)
return ptr, err
}
// RequestBodyRef represents either a RequestBody or a $ref to a RequestBody.
// When serializing and both fields are set, Ref is preferred over Value.
type RequestBodyRef struct {
Ref string
Value *RequestBody
extra []string
}
var _ jsonpointer.JSONPointable = (*RequestBodyRef)(nil)
func (x *RequestBodyRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil }
// MarshalYAML returns the YAML encoding of RequestBodyRef.
func (x RequestBodyRef) MarshalYAML() (interface{}, error) {
if ref := x.Ref; ref != "" {
return &Ref{Ref: ref}, nil
}
return x.Value, nil
}
// MarshalJSON returns the JSON encoding of RequestBodyRef.
func (x RequestBodyRef) MarshalJSON() ([]byte, error) {
if ref := x.Ref; ref != "" {
return json.Marshal(Ref{Ref: ref})
}
return x.Value.MarshalJSON()
}
// UnmarshalJSON sets RequestBodyRef to a copy of data.
func (x *RequestBodyRef) UnmarshalJSON(data []byte) error {
var refOnly Ref
if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" {
x.Ref = refOnly.Ref
if len(extra) != 0 {
x.extra = make([]string, 0, len(extra))
for key := range extra {
x.extra = append(x.extra, key)
}
sort.Strings(x.extra)
}
return nil
}
return json.Unmarshal(data, &x.Value)
}
// Validate returns an error if RequestBodyRef does not comply with the OpenAPI spec.
func (x *RequestBodyRef) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if extra := x.extra; len(extra) != 0 {
extras := make([]string, 0, len(extra))
allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed
for _, ex := range extra {
if allowed != nil {
if _, ok := allowed[ex]; ok {
continue
}
}
extras = append(extras, ex)
}
if len(extras) != 0 {
return fmt.Errorf("extra sibling fields: %+v", extras)
}
}
if v := x.Value; v != nil {
return v.Validate(ctx)
}
return foundUnresolvedRef(x.Ref)
}
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (x *RequestBodyRef) JSONLookup(token string) (interface{}, error) {
if token == "$ref" {
return x.Ref, nil
}
ptr, _, err := jsonpointer.GetForToken(x.Value, token)
return ptr, err
}
// ResponseRef represents either a Response or a $ref to a Response.
// When serializing and both fields are set, Ref is preferred over Value.
type ResponseRef struct {
Ref string
Value *Response
extra []string
}
var _ jsonpointer.JSONPointable = (*ResponseRef)(nil)
func (x *ResponseRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil }
// MarshalYAML returns the YAML encoding of ResponseRef.
func (x ResponseRef) MarshalYAML() (interface{}, error) {
if ref := x.Ref; ref != "" {
return &Ref{Ref: ref}, nil
}
return x.Value, nil
}
// MarshalJSON returns the JSON encoding of ResponseRef.
func (x ResponseRef) MarshalJSON() ([]byte, error) {
if ref := x.Ref; ref != "" {
return json.Marshal(Ref{Ref: ref})
}
return x.Value.MarshalJSON()
}
// UnmarshalJSON sets ResponseRef to a copy of data.
func (x *ResponseRef) UnmarshalJSON(data []byte) error {
var refOnly Ref
if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" {
x.Ref = refOnly.Ref
if len(extra) != 0 {
x.extra = make([]string, 0, len(extra))
for key := range extra {
x.extra = append(x.extra, key)
}
sort.Strings(x.extra)
}
return nil
}
return json.Unmarshal(data, &x.Value)
}
// Validate returns an error if ResponseRef does not comply with the OpenAPI spec.
func (x *ResponseRef) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if extra := x.extra; len(extra) != 0 {
extras := make([]string, 0, len(extra))
allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed
for _, ex := range extra {
if allowed != nil {
if _, ok := allowed[ex]; ok {
continue
}
}
extras = append(extras, ex)
}
if len(extras) != 0 {
return fmt.Errorf("extra sibling fields: %+v", extras)
}
}
if v := x.Value; v != nil {
return v.Validate(ctx)
}
return foundUnresolvedRef(x.Ref)
}
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (x *ResponseRef) JSONLookup(token string) (interface{}, error) {
if token == "$ref" {
return x.Ref, nil
}
ptr, _, err := jsonpointer.GetForToken(x.Value, token)
return ptr, err
}
// SchemaRef represents either a Schema or a $ref to a Schema.
// When serializing and both fields are set, Ref is preferred over Value.
type SchemaRef struct {
Ref string
Value *Schema
extra []string
}
var _ jsonpointer.JSONPointable = (*SchemaRef)(nil)
func (x *SchemaRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil }
// MarshalYAML returns the YAML encoding of SchemaRef.
func (x SchemaRef) MarshalYAML() (interface{}, error) {
if ref := x.Ref; ref != "" {
return &Ref{Ref: ref}, nil
}
return x.Value, nil
}
// MarshalJSON returns the JSON encoding of SchemaRef.
func (x SchemaRef) MarshalJSON() ([]byte, error) {
if ref := x.Ref; ref != "" {
return json.Marshal(Ref{Ref: ref})
}
return x.Value.MarshalJSON()
}
// UnmarshalJSON sets SchemaRef to a copy of data.
func (x *SchemaRef) UnmarshalJSON(data []byte) error {
var refOnly Ref
if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" {
x.Ref = refOnly.Ref
if len(extra) != 0 {
x.extra = make([]string, 0, len(extra))
for key := range extra {
x.extra = append(x.extra, key)
}
sort.Strings(x.extra)
}
return nil
}
return json.Unmarshal(data, &x.Value)
}
// Validate returns an error if SchemaRef does not comply with the OpenAPI spec.
func (x *SchemaRef) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if extra := x.extra; len(extra) != 0 {
extras := make([]string, 0, len(extra))
allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed
for _, ex := range extra {
if allowed != nil {
if _, ok := allowed[ex]; ok {
continue
}
}
extras = append(extras, ex)
}
if len(extras) != 0 {
return fmt.Errorf("extra sibling fields: %+v", extras)
}
}
if v := x.Value; v != nil {
return v.Validate(ctx)
}
return foundUnresolvedRef(x.Ref)
}
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (x *SchemaRef) JSONLookup(token string) (interface{}, error) {
if token == "$ref" {
return x.Ref, nil
}
ptr, _, err := jsonpointer.GetForToken(x.Value, token)
return ptr, err
}
// SecuritySchemeRef represents either a SecurityScheme or a $ref to a SecurityScheme.
// When serializing and both fields are set, Ref is preferred over Value.
type SecuritySchemeRef struct {
Ref string
Value *SecurityScheme
extra []string
}
var _ jsonpointer.JSONPointable = (*SecuritySchemeRef)(nil)
func (x *SecuritySchemeRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil }
// MarshalYAML returns the YAML encoding of SecuritySchemeRef.
func (x SecuritySchemeRef) MarshalYAML() (interface{}, error) {
if ref := x.Ref; ref != "" {
return &Ref{Ref: ref}, nil
}
return x.Value, nil
}
// MarshalJSON returns the JSON encoding of SecuritySchemeRef.
func (x SecuritySchemeRef) MarshalJSON() ([]byte, error) {
if ref := x.Ref; ref != "" {
return json.Marshal(Ref{Ref: ref})
}
return x.Value.MarshalJSON()
}
// UnmarshalJSON sets SecuritySchemeRef to a copy of data.
func (x *SecuritySchemeRef) UnmarshalJSON(data []byte) error {
var refOnly Ref
if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" {
x.Ref = refOnly.Ref
if len(extra) != 0 {
x.extra = make([]string, 0, len(extra))
for key := range extra {
x.extra = append(x.extra, key)
}
sort.Strings(x.extra)
}
return nil
}
return json.Unmarshal(data, &x.Value)
}
// Validate returns an error if SecuritySchemeRef does not comply with the OpenAPI spec.
func (x *SecuritySchemeRef) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if extra := x.extra; len(extra) != 0 {
extras := make([]string, 0, len(extra))
allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed
for _, ex := range extra {
if allowed != nil {
if _, ok := allowed[ex]; ok {
continue
}
}
extras = append(extras, ex)
}
if len(extras) != 0 {
return fmt.Errorf("extra sibling fields: %+v", extras)
}
}
if v := x.Value; v != nil {
return v.Validate(ctx)
}
return foundUnresolvedRef(x.Ref)
}
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (x *SecuritySchemeRef) JSONLookup(token string) (interface{}, error) {
if token == "$ref" {
return x.Ref, nil
}
ptr, _, err := jsonpointer.GetForToken(x.Value, token)
return ptr, err
}
kin-openapi-0.124.0/openapi3/refs_test.go 0000664 0000000 0000000 00000020223 14604223742 0020136 0 ustar 00root root 0000000 0000000 package openapi3
import (
"reflect"
"testing"
"github.com/go-openapi/jsonpointer"
"github.com/stretchr/testify/require"
)
func TestIssue222(t *testing.T) {
spec := []byte(`
openapi: 3.0.0
info:
version: 1.0.0
title: Swagger Petstore
license:
name: MIT
servers:
- url: 'http://petstore.swagger.io/v1'
paths:
/pets:
get:
summary: List all pets
operationId: listPets
tags:
- pets
parameters:
- name: limit
in: query
description: How many items to return at one time (max 100)
required: false
schema:
type: integer
format: int32
responses:
'200': # <--------------- PANIC HERE
post:
summary: Create a pet
operationId: createPets
tags:
- pets
responses:
'201':
description: Null response
default:
description: unexpected error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'/pets/{petId}':
get:
summary: Info for a specific pet
operationId: showPetById
tags:
- pets
parameters:
- name: petId
in: path
required: true
description: The id of the pet to retrieve
schema:
type: string
responses:
'200':
description: Expected response to a valid request
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
default:
description: unexpected error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
components:
schemas:
Pet:
type: object
required:
- id
- name
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
Pets:
type: array
items:
$ref: '#/components/schemas/Pet'
Error:
type: object
required:
- code
- message
properties:
code:
type: integer
format: int32
message:
type: string
`[1:])
loader := NewLoader()
doc, err := loader.LoadFromData(spec)
require.EqualError(t, err, `invalid response: value MUST be an object`)
require.Nil(t, doc)
}
func TestIssue247(t *testing.T) {
spec := []byte(`
openapi: 3.0.2
info:
title: Swagger Petstore - OpenAPI 3.0
license:
name: Apache 2.0
url: http://www.apache.org/licenses/LICENSE-2.0.html
version: 1.0.5
servers:
- url: /api/v3
tags:
- name: pet
description: Everything about your Pets
externalDocs:
description: Find out more
url: http://swagger.io
- name: store
description: Operations about user
- name: user
description: Access to Petstore orders
externalDocs:
description: Find out more about our store
url: http://swagger.io
paths:
/pet:
put:
tags:
- pet
summary: Update an existing pet
description: Update an existing pet by Id
operationId: updatePet
requestBody:
description: Update an existent pet in the store
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
application/xml:
schema:
$ref: '#/components/schemas/Pet'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/Pet'
required: true
responses:
"200":
description: Successful operation
content:
application/xml:
schema:
$ref: '#/components/schemas/Pet'
application/json:
schema:
$ref: '#/components/schemas/Pet'
"400":
description: Invalid ID supplied
"404":
description: Pet not found
"405":
description: Validation exception
security:
- petstore_auth:
- write:pets
- read:pets
components:
schemas:
Pet:
type: object
required:
- id
- name
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
Pets:
type: array
items:
$ref: '#/components/schemas/Pet'
Error:
type: object
required:
- code
- message
properties:
code:
type: integer
format: int32
message:
type: string
OneOfTest:
type: object
oneOf:
- type: string
- type: integer
format: int32
`[1:])
loader := NewLoader()
doc, err := loader.LoadFromData(spec)
require.NoError(t, err)
require.NotNil(t, doc)
err = doc.Validate(loader.Context)
require.NoError(t, err)
var ptr jsonpointer.Pointer
var v interface{}
var kind reflect.Kind
ptr, err = jsonpointer.New("/paths")
require.NoError(t, err)
v, kind, err = ptr.Get(doc)
require.NoError(t, err)
require.NotNil(t, v)
require.IsType(t, &Paths{}, v)
require.Equal(t, reflect.TypeOf(&Paths{}).Kind(), kind)
ptr, err = jsonpointer.New("/paths/~1pet")
require.NoError(t, err)
v, kind, err = ptr.Get(doc)
require.NoError(t, err)
require.NotNil(t, v)
require.IsType(t, &PathItem{}, v)
require.Equal(t, reflect.TypeOf(&PathItem{}).Kind(), kind)
ptr, err = jsonpointer.New("/paths/~1pet/put")
require.NoError(t, err)
v, kind, err = ptr.Get(doc)
require.NoError(t, err)
require.NotNil(t, v)
require.IsType(t, &Operation{}, v)
require.Equal(t, reflect.TypeOf(&Operation{}).Kind(), kind)
ptr, err = jsonpointer.New("/paths/~1pet/put/responses")
require.NoError(t, err)
v, kind, err = ptr.Get(doc)
require.NoError(t, err)
require.NotNil(t, v)
require.IsType(t, &Responses{}, v)
require.Equal(t, reflect.TypeOf(&Responses{}).Kind(), kind)
ptr, err = jsonpointer.New("/paths/~1pet/put/responses/200")
require.NoError(t, err)
v, kind, err = ptr.Get(doc)
require.NoError(t, err)
require.NotNil(t, v)
require.IsType(t, &Response{}, v)
require.Equal(t, reflect.TypeOf(&Response{}).Kind(), kind)
ptr, err = jsonpointer.New("/paths/~1pet/put/responses/200/content")
require.NoError(t, err)
v, kind, err = ptr.Get(doc)
require.NoError(t, err)
require.NotNil(t, v)
require.IsType(t, Content{}, v)
require.Equal(t, reflect.TypeOf(Content{}).Kind(), kind)
ptr, err = jsonpointer.New("/paths/~1pet/put/responses/200/content/application~1json/schema")
require.NoError(t, err)
v, kind, err = ptr.Get(doc)
require.NoError(t, err)
require.NotNil(t, v)
require.IsType(t, &Ref{}, v)
require.Equal(t, reflect.Ptr, kind)
require.Equal(t, "#/components/schemas/Pet", v.(*Ref).Ref)
ptr, err = jsonpointer.New("/components/schemas/Pets/items")
require.NoError(t, err)
v, kind, err = ptr.Get(doc)
require.NoError(t, err)
require.NotNil(t, v)
require.IsType(t, &Ref{}, v)
require.Equal(t, reflect.Ptr, kind)
require.Equal(t, "#/components/schemas/Pet", v.(*Ref).Ref)
ptr, err = jsonpointer.New("/components/schemas/Error/properties/code")
require.NoError(t, err)
v, kind, err = ptr.Get(doc)
require.NoError(t, err)
require.NotNil(t, v)
require.IsType(t, &Schema{}, v)
require.Equal(t, reflect.Ptr, kind)
require.Equal(t, &Types{"integer"}, v.(*Schema).Type)
ptr, err = jsonpointer.New("/components/schemas/OneOfTest/oneOf/0")
require.NoError(t, err)
v, kind, err = ptr.Get(doc)
require.NoError(t, err)
require.NotNil(t, v)
require.IsType(t, &Schema{}, v)
require.Equal(t, reflect.Ptr, kind)
require.Equal(t, &Types{"string"}, v.(*Schema).Type)
ptr, err = jsonpointer.New("/components/schemas/OneOfTest/oneOf/1")
require.NoError(t, err)
v, kind, err = ptr.Get(doc)
require.NoError(t, err)
require.NotNil(t, v)
require.IsType(t, &Schema{}, v)
require.Equal(t, reflect.Ptr, kind)
require.Equal(t, &Types{"integer"}, v.(*Schema).Type)
ptr, err = jsonpointer.New("/components/schemas/OneOfTest/oneOf/5")
require.NoError(t, err)
_, _, err = ptr.Get(doc)
require.Error(t, err)
ptr, err = jsonpointer.New("/components/schemas/OneOfTest/oneOf/-1")
require.NoError(t, err)
_, _, err = ptr.Get(doc)
require.Error(t, err)
}
kin-openapi-0.124.0/openapi3/request_body.go 0000664 0000000 0000000 00000007221 14604223742 0020650 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"encoding/json"
"errors"
)
// RequestBody is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#request-body-object
type RequestBody struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Required bool `json:"required,omitempty" yaml:"required,omitempty"`
Content Content `json:"content" yaml:"content"`
}
func NewRequestBody() *RequestBody {
return &RequestBody{}
}
func (requestBody *RequestBody) WithDescription(value string) *RequestBody {
requestBody.Description = value
return requestBody
}
func (requestBody *RequestBody) WithRequired(value bool) *RequestBody {
requestBody.Required = value
return requestBody
}
func (requestBody *RequestBody) WithContent(content Content) *RequestBody {
requestBody.Content = content
return requestBody
}
func (requestBody *RequestBody) WithSchemaRef(value *SchemaRef, consumes []string) *RequestBody {
requestBody.Content = NewContentWithSchemaRef(value, consumes)
return requestBody
}
func (requestBody *RequestBody) WithSchema(value *Schema, consumes []string) *RequestBody {
requestBody.Content = NewContentWithSchema(value, consumes)
return requestBody
}
func (requestBody *RequestBody) WithJSONSchemaRef(value *SchemaRef) *RequestBody {
requestBody.Content = NewContentWithJSONSchemaRef(value)
return requestBody
}
func (requestBody *RequestBody) WithJSONSchema(value *Schema) *RequestBody {
requestBody.Content = NewContentWithJSONSchema(value)
return requestBody
}
func (requestBody *RequestBody) WithFormDataSchemaRef(value *SchemaRef) *RequestBody {
requestBody.Content = NewContentWithFormDataSchemaRef(value)
return requestBody
}
func (requestBody *RequestBody) WithFormDataSchema(value *Schema) *RequestBody {
requestBody.Content = NewContentWithFormDataSchema(value)
return requestBody
}
func (requestBody *RequestBody) GetMediaType(mediaType string) *MediaType {
m := requestBody.Content
if m == nil {
return nil
}
return m[mediaType]
}
// MarshalJSON returns the JSON encoding of RequestBody.
func (requestBody RequestBody) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{}, 3+len(requestBody.Extensions))
for k, v := range requestBody.Extensions {
m[k] = v
}
if x := requestBody.Description; x != "" {
m["description"] = requestBody.Description
}
if x := requestBody.Required; x {
m["required"] = x
}
if x := requestBody.Content; true {
m["content"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets RequestBody to a copy of data.
func (requestBody *RequestBody) UnmarshalJSON(data []byte) error {
type RequestBodyBis RequestBody
var x RequestBodyBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "description")
delete(x.Extensions, "required")
delete(x.Extensions, "content")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*requestBody = RequestBody(x)
return nil
}
// Validate returns an error if RequestBody does not comply with the OpenAPI spec.
func (requestBody *RequestBody) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if requestBody.Content == nil {
return errors.New("content of the request body is required")
}
if vo := getValidationOptions(ctx); !vo.examplesValidationDisabled {
vo.examplesValidationAsReq, vo.examplesValidationAsRes = true, false
}
if err := requestBody.Content.Validate(ctx); err != nil {
return err
}
return validateExtensions(ctx, requestBody.Extensions)
}
kin-openapi-0.124.0/openapi3/response.go 0000664 0000000 0000000 00000014472 14604223742 0020007 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"encoding/json"
"errors"
"sort"
"strconv"
)
// Responses is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#responses-object
type Responses struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
m map[string]*ResponseRef
}
// NewResponses builds a responses object with response objects in insertion order.
// Given no arguments, NewResponses returns a valid responses object containing a default match-all reponse.
func NewResponses(opts ...NewResponsesOption) *Responses {
if len(opts) == 0 {
return NewResponses(WithName("default", NewResponse().WithDescription("")))
}
responses := NewResponsesWithCapacity(len(opts))
for _, opt := range opts {
opt(responses)
}
return responses
}
// NewResponsesOption describes options to NewResponses func
type NewResponsesOption func(*Responses)
// WithStatus adds a status code keyed ResponseRef
func WithStatus(status int, responseRef *ResponseRef) NewResponsesOption {
return func(responses *Responses) {
if r := responseRef; r != nil {
code := strconv.FormatInt(int64(status), 10)
responses.Set(code, r)
}
}
}
// WithName adds a name-keyed Response
func WithName(name string, response *Response) NewResponsesOption {
return func(responses *Responses) {
if r := response; r != nil && name != "" {
responses.Set(name, &ResponseRef{Value: r})
}
}
}
// Default returns the default response
func (responses *Responses) Default() *ResponseRef {
return responses.Value("default")
}
// Status returns a ResponseRef for the given status
// If an exact match isn't initially found a patterned field is checked using
// the first digit to determine the range (eg: 201 to 2XX)
// See https://spec.openapis.org/oas/v3.0.3#patterned-fields-0
func (responses *Responses) Status(status int) *ResponseRef {
st := strconv.FormatInt(int64(status), 10)
if rref := responses.Value(st); rref != nil {
return rref
}
if 99 < status && status < 600 {
st = string(st[0]) + "XX"
switch st {
case "1XX", "2XX", "3XX", "4XX", "5XX":
return responses.Value(st)
}
}
return nil
}
// Validate returns an error if Responses does not comply with the OpenAPI spec.
func (responses *Responses) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if responses.Len() == 0 {
return errors.New("the responses object MUST contain at least one response code")
}
keys := make([]string, 0, responses.Len())
for key := range responses.Map() {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
v := responses.Value(key)
if err := v.Validate(ctx); err != nil {
return err
}
}
return validateExtensions(ctx, responses.Extensions)
}
// Support YAML Marshaler interface for gopkg.in/yaml
func (responses *Responses) MarshalYAML() (any, error) {
res := make(map[string]any, len(responses.Extensions)+len(responses.m))
for k, v := range responses.Extensions {
res[k] = v
}
for k, v := range responses.m {
res[k] = v
}
return res, nil
}
// Response is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#response-object
type Response struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Description *string `json:"description,omitempty" yaml:"description,omitempty"`
Headers Headers `json:"headers,omitempty" yaml:"headers,omitempty"`
Content Content `json:"content,omitempty" yaml:"content,omitempty"`
Links Links `json:"links,omitempty" yaml:"links,omitempty"`
}
func NewResponse() *Response {
return &Response{}
}
func (response *Response) WithDescription(value string) *Response {
response.Description = &value
return response
}
func (response *Response) WithContent(content Content) *Response {
response.Content = content
return response
}
func (response *Response) WithJSONSchema(schema *Schema) *Response {
response.Content = NewContentWithJSONSchema(schema)
return response
}
func (response *Response) WithJSONSchemaRef(schema *SchemaRef) *Response {
response.Content = NewContentWithJSONSchemaRef(schema)
return response
}
// MarshalJSON returns the JSON encoding of Response.
func (response Response) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{}, 4+len(response.Extensions))
for k, v := range response.Extensions {
m[k] = v
}
if x := response.Description; x != nil {
m["description"] = x
}
if x := response.Headers; len(x) != 0 {
m["headers"] = x
}
if x := response.Content; len(x) != 0 {
m["content"] = x
}
if x := response.Links; len(x) != 0 {
m["links"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets Response to a copy of data.
func (response *Response) UnmarshalJSON(data []byte) error {
type ResponseBis Response
var x ResponseBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "description")
delete(x.Extensions, "headers")
delete(x.Extensions, "content")
delete(x.Extensions, "links")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*response = Response(x)
return nil
}
// Validate returns an error if Response does not comply with the OpenAPI spec.
func (response *Response) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if response.Description == nil {
return errors.New("a short description of the response is required")
}
if vo := getValidationOptions(ctx); !vo.examplesValidationDisabled {
vo.examplesValidationAsReq, vo.examplesValidationAsRes = false, true
}
if content := response.Content; content != nil {
if err := content.Validate(ctx); err != nil {
return err
}
}
headers := make([]string, 0, len(response.Headers))
for name := range response.Headers {
headers = append(headers, name)
}
sort.Strings(headers)
for _, name := range headers {
header := response.Headers[name]
if err := header.Validate(ctx); err != nil {
return err
}
}
links := make([]string, 0, len(response.Links))
for name := range response.Links {
links = append(links, name)
}
sort.Strings(links)
for _, name := range links {
link := response.Links[name]
if err := link.Validate(ctx); err != nil {
return err
}
}
return validateExtensions(ctx, response.Extensions)
}
kin-openapi-0.124.0/openapi3/response_issue224_test.go 0000664 0000000 0000000 00000034640 14604223742 0022505 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"testing"
"github.com/stretchr/testify/require"
)
func TestEmptyResponsesAreInvalid(t *testing.T) {
spec := []byte(`
{
"openapi": "3.0.0",
"servers": [
{
"url": "http://petstore.swagger.io/v2"
}
],
"info": {
"description": ":dog: :cat: :rabbit: This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key to test the authorization filters.",
"version": "1.0.0",
"title": "Swagger Petstore",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"email": "apiteam@swagger.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
}
},
"tags": [
{
"name": "pet",
"description": "Everything about your Pets",
"externalDocs": {
"description": "Find out more",
"url": "http://swagger.io"
}
},
{
"name": "store",
"description": "Access to Petstore orders"
},
{
"name": "user",
"description": "Operations about user",
"externalDocs": {
"description": "Find out more about our store",
"url": "http://swagger.io"
}
}
],
"paths": {
"/pet": {
"post": {
"tags": [
"pet"
],
"summary": "Add a new pet to the store",
"description": "",
"operationId": "addPet",
"responses": {
},
"security": [
{
"petstore_auth": [
"write:pets",
"read:pets"
]
}
],
"requestBody": {
"$ref": "#/components/requestBodies/Pet"
},
"parameters": []
},
"put": {
"tags": [
"pet"
],
"summary": "Update an existing pet",
"description": "",
"operationId": "updatePet",
"responses": {
},
"security": [
{
"petstore_auth": [
"write:pets",
"read:pets"
]
}
],
"requestBody": {
"$ref": "#/components/requestBodies/Pet"
},
"parameters": []
}
},
"/pet/{petId}": {
"get": {
"tags": [
"pet"
],
"summary": "Find pet by ID",
"description": "Returns a single pet",
"operationId": "getPetById",
"parameters": [
{
"name": "petId",
"in": "path",
"description": "ID of pet to return",
"required": true,
"schema": {
"type": "integer",
"format": "int64"
}
}
],
"responses": {
},
"security": [
{
"api_key": []
}
]
},
"post": {
"tags": [
"pet"
],
"summary": "Updates a pet in the store with form data",
"description": "",
"operationId": "updatePetWithForm",
"parameters": [
{
"name": "petId",
"in": "path",
"description": "ID of pet that needs to be updated",
"required": true,
"schema": {
"type": "integer",
"format": "int64"
}
}
],
"responses": {
},
"security": [
{
"petstore_auth": [
"write:pets",
"read:pets"
]
}
],
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"type": "object",
"properties": {
"name": {
"description": "Updated name of the pet",
"type": "string"
},
"status": {
"description": "Updated status of the pet",
"type": "string"
}
}
}
}
}
}
},
"delete": {
"tags": [
"pet"
],
"summary": "Deletes a pet",
"description": "",
"operationId": "deletePet",
"parameters": [
{
"name": "api_key",
"in": "header",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "petId",
"in": "path",
"description": "Pet id to delete",
"required": true,
"schema": {
"type": "integer",
"format": "int64"
}
}
],
"responses": {
},
"security": [
{
"petstore_auth": [
"write:pets",
"read:pets"
]
}
]
}
}
},
"externalDocs": {
"description": "See AsyncAPI example",
"url": "https://mermade.github.io/shins/asyncapi.html"
},
"components": {
"schemas": {
"Order": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"petId": {
"type": "integer",
"format": "int64"
},
"quantity": {
"type": "integer",
"format": "int32"
},
"shipDate": {
"type": "string",
"format": "date-time"
},
"status": {
"type": "string",
"description": "Order Status",
"enum": [
"placed",
"approved",
"delivered"
]
},
"complete": {
"type": "boolean",
"default": false
}
},
"xml": {
"name": "Order"
}
},
"Category": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"name": {
"type": "string"
}
},
"xml": {
"name": "Category"
}
},
"User": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"username": {
"type": "string"
},
"firstName": {
"type": "string"
},
"lastName": {
"type": "string"
},
"email": {
"type": "string"
},
"password": {
"type": "string"
},
"phone": {
"type": "string"
},
"userStatus": {
"type": "integer",
"format": "int32",
"description": "User Status"
}
},
"xml": {
"name": "User"
}
},
"Tag": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"name": {
"type": "string"
}
},
"xml": {
"name": "Tag"
}
},
"Pet": {
"type": "object",
"required": [
"name",
"photoUrls"
],
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"category": {
"$ref": "#/components/schemas/Category"
},
"name": {
"type": "string",
"example": "doggie"
},
"photoUrls": {
"type": "array",
"xml": {
"name": "photoUrl",
"wrapped": true
},
"items": {
"type": "string"
}
},
"tags": {
"type": "array",
"xml": {
"name": "tag",
"wrapped": true
},
"items": {
"$ref": "#/components/schemas/Tag"
}
},
"status": {
"type": "string",
"description": "pet status in the store",
"enum": [
"available",
"pending",
"sold"
]
}
},
"xml": {
"name": "Pet"
}
},
"ApiResponse": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"type": {
"type": "string"
},
"message": {
"type": "string"
}
}
}
},
"requestBodies": {
"Pet": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Pet"
}
},
"application/xml": {
"schema": {
"$ref": "#/components/schemas/Pet"
}
}
},
"description": "Pet object that needs to be added to the store",
"required": true
},
"UserArray": {
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/User"
}
}
}
},
"description": "List of user object",
"required": true
}
},
"securitySchemes": {
"petstore_auth": {
"type": "oauth2",
"flows": {
"implicit": {
"authorizationUrl": "http://petstore.swagger.io/oauth/dialog",
"scopes": {
"write:pets": "modify pets in your account",
"read:pets": "read your pets"
}
}
}
},
"api_key": {
"type": "apiKey",
"name": "api_key",
"in": "header"
}
},
"links": {},
"callbacks": {}
},
"security": []
}
`[1:])
loader := NewLoader()
doc, err := loader.LoadFromData(spec)
require.NoError(t, err)
require.Equal(t, doc.ExternalDocs.Description, "See AsyncAPI example")
err = doc.Validate(context.Background())
require.EqualError(t, err, `invalid paths: invalid path /pet: invalid operation POST: the responses object MUST contain at least one response code`)
}
kin-openapi-0.124.0/openapi3/schema.go 0000664 0000000 0000000 00000162222 14604223742 0017406 0 ustar 00root root 0000000 0000000 package openapi3
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"math"
"math/big"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
"sync"
"unicode/utf16"
"github.com/go-openapi/jsonpointer"
"github.com/mohae/deepcopy"
)
const (
TypeArray = "array"
TypeBoolean = "boolean"
TypeInteger = "integer"
TypeNumber = "number"
TypeObject = "object"
TypeString = "string"
TypeNull = "null"
// constants for integer formats
formatMinInt32 = float64(math.MinInt32)
formatMaxInt32 = float64(math.MaxInt32)
formatMinInt64 = float64(math.MinInt64)
formatMaxInt64 = float64(math.MaxInt64)
)
var (
// SchemaErrorDetailsDisabled disables printing of details about schema errors.
SchemaErrorDetailsDisabled = false
errSchema = errors.New("input does not match the schema")
// ErrOneOfConflict is the SchemaError Origin when data matches more than one oneOf schema
ErrOneOfConflict = errors.New("input matches more than one oneOf schemas")
// ErrSchemaInputNaN may be returned when validating a number
ErrSchemaInputNaN = errors.New("floating point NaN is not allowed")
// ErrSchemaInputInf may be returned when validating a number
ErrSchemaInputInf = errors.New("floating point Inf is not allowed")
compiledPatterns sync.Map
)
// NewSchemaRef simply builds a SchemaRef
func NewSchemaRef(ref string, value *Schema) *SchemaRef {
return &SchemaRef{
Ref: ref,
Value: value,
}
}
type SchemaRefs []*SchemaRef
var _ jsonpointer.JSONPointable = (*SchemaRefs)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (s SchemaRefs) JSONLookup(token string) (interface{}, error) {
i, err := strconv.ParseUint(token, 10, 64)
if err != nil {
return nil, err
}
if i >= uint64(len(s)) {
return nil, fmt.Errorf("index out of range: %d", i)
}
ref := s[i]
if ref == nil || ref.Ref != "" {
return &Ref{Ref: ref.Ref}, nil
}
return ref.Value, nil
}
// Schema is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#schema-object
type Schema struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
OneOf SchemaRefs `json:"oneOf,omitempty" yaml:"oneOf,omitempty"`
AnyOf SchemaRefs `json:"anyOf,omitempty" yaml:"anyOf,omitempty"`
AllOf SchemaRefs `json:"allOf,omitempty" yaml:"allOf,omitempty"`
Not *SchemaRef `json:"not,omitempty" yaml:"not,omitempty"`
Type *Types `json:"type,omitempty" yaml:"type,omitempty"`
Title string `json:"title,omitempty" yaml:"title,omitempty"`
Format string `json:"format,omitempty" yaml:"format,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Enum []interface{} `json:"enum,omitempty" yaml:"enum,omitempty"`
Default interface{} `json:"default,omitempty" yaml:"default,omitempty"`
Example interface{} `json:"example,omitempty" yaml:"example,omitempty"`
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
// Array-related, here for struct compactness
UniqueItems bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"`
// Number-related, here for struct compactness
ExclusiveMin bool `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"`
ExclusiveMax bool `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"`
// Properties
Nullable bool `json:"nullable,omitempty" yaml:"nullable,omitempty"`
ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"`
WriteOnly bool `json:"writeOnly,omitempty" yaml:"writeOnly,omitempty"`
AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"`
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
XML *XML `json:"xml,omitempty" yaml:"xml,omitempty"`
// Number
Min *float64 `json:"minimum,omitempty" yaml:"minimum,omitempty"`
Max *float64 `json:"maximum,omitempty" yaml:"maximum,omitempty"`
MultipleOf *float64 `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"`
// String
MinLength uint64 `json:"minLength,omitempty" yaml:"minLength,omitempty"`
MaxLength *uint64 `json:"maxLength,omitempty" yaml:"maxLength,omitempty"`
Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"`
// Array
MinItems uint64 `json:"minItems,omitempty" yaml:"minItems,omitempty"`
MaxItems *uint64 `json:"maxItems,omitempty" yaml:"maxItems,omitempty"`
Items *SchemaRef `json:"items,omitempty" yaml:"items,omitempty"`
// Object
Required []string `json:"required,omitempty" yaml:"required,omitempty"`
Properties Schemas `json:"properties,omitempty" yaml:"properties,omitempty"`
MinProps uint64 `json:"minProperties,omitempty" yaml:"minProperties,omitempty"`
MaxProps *uint64 `json:"maxProperties,omitempty" yaml:"maxProperties,omitempty"`
AdditionalProperties AdditionalProperties `json:"additionalProperties,omitempty" yaml:"additionalProperties,omitempty"`
Discriminator *Discriminator `json:"discriminator,omitempty" yaml:"discriminator,omitempty"`
}
type Types []string
func (types *Types) Is(typ string) bool {
return types != nil && len(*types) == 1 && (*types)[0] == typ
}
func (types *Types) Slice() []string {
if types == nil {
return nil
}
return *types
}
func (pTypes *Types) Includes(typ string) bool {
if pTypes == nil {
return false
}
types := *pTypes
for _, candidate := range types {
if candidate == typ {
return true
}
}
return false
}
func (types *Types) Permits(typ string) bool {
if types == nil {
return true
}
return types.Includes(typ)
}
func (pTypes *Types) MarshalJSON() ([]byte, error) {
x, err := pTypes.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
func (pTypes *Types) MarshalYAML() (interface{}, error) {
if pTypes == nil {
return nil, nil
}
types := *pTypes
switch len(types) {
case 0:
return nil, nil
case 1:
return types[0], nil
default:
return []string(types), nil
}
}
func (types *Types) UnmarshalJSON(data []byte) error {
var strings []string
if err := json.Unmarshal(data, &strings); err != nil {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return unmarshalError(err)
}
strings = []string{s}
}
*types = strings
return nil
}
type AdditionalProperties struct {
Has *bool
Schema *SchemaRef
}
// MarshalYAML returns the YAML encoding of AdditionalProperties.
func (addProps AdditionalProperties) MarshalYAML() (interface{}, error) {
if x := addProps.Has; x != nil {
if *x {
return true, nil
}
return false, nil
}
if x := addProps.Schema; x != nil {
return x.Value, nil
}
return nil, nil
}
// MarshalJSON returns the JSON encoding of AdditionalProperties.
func (addProps AdditionalProperties) MarshalJSON() ([]byte, error) {
if x := addProps.Has; x != nil {
if *x {
return []byte("true"), nil
}
return []byte("false"), nil
}
if x := addProps.Schema; x != nil {
return json.Marshal(x)
}
return nil, nil
}
// UnmarshalJSON sets AdditionalProperties to a copy of data.
func (addProps *AdditionalProperties) UnmarshalJSON(data []byte) error {
var x interface{}
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
switch y := x.(type) {
case nil:
case bool:
addProps.Has = &y
case map[string]interface{}:
if len(y) == 0 {
addProps.Schema = &SchemaRef{Value: &Schema{}}
} else {
buf := new(bytes.Buffer)
json.NewEncoder(buf).Encode(y)
if err := json.NewDecoder(buf).Decode(&addProps.Schema); err != nil {
return err
}
}
default:
return errors.New("cannot unmarshal additionalProperties: value must be either a schema object or a boolean")
}
return nil
}
var _ jsonpointer.JSONPointable = (*Schema)(nil)
func NewSchema() *Schema {
return &Schema{}
}
// MarshalJSON returns the JSON encoding of Schema.
func (schema Schema) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{}, 36+len(schema.Extensions))
for k, v := range schema.Extensions {
m[k] = v
}
if x := schema.OneOf; len(x) != 0 {
m["oneOf"] = x
}
if x := schema.AnyOf; len(x) != 0 {
m["anyOf"] = x
}
if x := schema.AllOf; len(x) != 0 {
m["allOf"] = x
}
if x := schema.Not; x != nil {
m["not"] = x
}
if x := schema.Type; x != nil {
m["type"] = x
}
if x := schema.Title; len(x) != 0 {
m["title"] = x
}
if x := schema.Format; len(x) != 0 {
m["format"] = x
}
if x := schema.Description; len(x) != 0 {
m["description"] = x
}
if x := schema.Enum; len(x) != 0 {
m["enum"] = x
}
if x := schema.Default; x != nil {
m["default"] = x
}
if x := schema.Example; x != nil {
m["example"] = x
}
if x := schema.ExternalDocs; x != nil {
m["externalDocs"] = x
}
// Array-related
if x := schema.UniqueItems; x {
m["uniqueItems"] = x
}
// Number-related
if x := schema.ExclusiveMin; x {
m["exclusiveMinimum"] = x
}
if x := schema.ExclusiveMax; x {
m["exclusiveMaximum"] = x
}
// Properties
if x := schema.Nullable; x {
m["nullable"] = x
}
if x := schema.ReadOnly; x {
m["readOnly"] = x
}
if x := schema.WriteOnly; x {
m["writeOnly"] = x
}
if x := schema.AllowEmptyValue; x {
m["allowEmptyValue"] = x
}
if x := schema.Deprecated; x {
m["deprecated"] = x
}
if x := schema.XML; x != nil {
m["xml"] = x
}
// Number
if x := schema.Min; x != nil {
m["minimum"] = x
}
if x := schema.Max; x != nil {
m["maximum"] = x
}
if x := schema.MultipleOf; x != nil {
m["multipleOf"] = x
}
// String
if x := schema.MinLength; x != 0 {
m["minLength"] = x
}
if x := schema.MaxLength; x != nil {
m["maxLength"] = x
}
if x := schema.Pattern; x != "" {
m["pattern"] = x
}
// Array
if x := schema.MinItems; x != 0 {
m["minItems"] = x
}
if x := schema.MaxItems; x != nil {
m["maxItems"] = x
}
if x := schema.Items; x != nil {
m["items"] = x
}
// Object
if x := schema.Required; len(x) != 0 {
m["required"] = x
}
if x := schema.Properties; len(x) != 0 {
m["properties"] = x
}
if x := schema.MinProps; x != 0 {
m["minProperties"] = x
}
if x := schema.MaxProps; x != nil {
m["maxProperties"] = x
}
if x := schema.AdditionalProperties; x.Has != nil || x.Schema != nil {
m["additionalProperties"] = &x
}
if x := schema.Discriminator; x != nil {
m["discriminator"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets Schema to a copy of data.
func (schema *Schema) UnmarshalJSON(data []byte) error {
type SchemaBis Schema
var x SchemaBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "oneOf")
delete(x.Extensions, "anyOf")
delete(x.Extensions, "allOf")
delete(x.Extensions, "not")
delete(x.Extensions, "type")
delete(x.Extensions, "title")
delete(x.Extensions, "format")
delete(x.Extensions, "description")
delete(x.Extensions, "enum")
delete(x.Extensions, "default")
delete(x.Extensions, "example")
delete(x.Extensions, "externalDocs")
// Array-related
delete(x.Extensions, "uniqueItems")
// Number-related
delete(x.Extensions, "exclusiveMinimum")
delete(x.Extensions, "exclusiveMaximum")
// Properties
delete(x.Extensions, "nullable")
delete(x.Extensions, "readOnly")
delete(x.Extensions, "writeOnly")
delete(x.Extensions, "allowEmptyValue")
delete(x.Extensions, "deprecated")
delete(x.Extensions, "xml")
// Number
delete(x.Extensions, "minimum")
delete(x.Extensions, "maximum")
delete(x.Extensions, "multipleOf")
// String
delete(x.Extensions, "minLength")
delete(x.Extensions, "maxLength")
delete(x.Extensions, "pattern")
// Array
delete(x.Extensions, "minItems")
delete(x.Extensions, "maxItems")
delete(x.Extensions, "items")
// Object
delete(x.Extensions, "required")
delete(x.Extensions, "properties")
delete(x.Extensions, "minProperties")
delete(x.Extensions, "maxProperties")
delete(x.Extensions, "additionalProperties")
delete(x.Extensions, "discriminator")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*schema = Schema(x)
if schema.Format == "date" {
// This is a fix for: https://github.com/getkin/kin-openapi/issues/697
if eg, ok := schema.Example.(string); ok {
schema.Example = strings.TrimSuffix(eg, "T00:00:00Z")
}
}
return nil
}
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (schema Schema) JSONLookup(token string) (interface{}, error) {
switch token {
case "additionalProperties":
if addProps := schema.AdditionalProperties.Has; addProps != nil {
return *addProps, nil
}
if addProps := schema.AdditionalProperties.Schema; addProps != nil {
if addProps.Ref != "" {
return &Ref{Ref: addProps.Ref}, nil
}
return addProps.Value, nil
}
case "not":
if schema.Not != nil {
if schema.Not.Ref != "" {
return &Ref{Ref: schema.Not.Ref}, nil
}
return schema.Not.Value, nil
}
case "items":
if schema.Items != nil {
if schema.Items.Ref != "" {
return &Ref{Ref: schema.Items.Ref}, nil
}
return schema.Items.Value, nil
}
case "oneOf":
return schema.OneOf, nil
case "anyOf":
return schema.AnyOf, nil
case "allOf":
return schema.AllOf, nil
case "type":
return schema.Type, nil
case "title":
return schema.Title, nil
case "format":
return schema.Format, nil
case "description":
return schema.Description, nil
case "enum":
return schema.Enum, nil
case "default":
return schema.Default, nil
case "example":
return schema.Example, nil
case "externalDocs":
return schema.ExternalDocs, nil
case "uniqueItems":
return schema.UniqueItems, nil
case "exclusiveMin":
return schema.ExclusiveMin, nil
case "exclusiveMax":
return schema.ExclusiveMax, nil
case "nullable":
return schema.Nullable, nil
case "readOnly":
return schema.ReadOnly, nil
case "writeOnly":
return schema.WriteOnly, nil
case "allowEmptyValue":
return schema.AllowEmptyValue, nil
case "xml":
return schema.XML, nil
case "deprecated":
return schema.Deprecated, nil
case "min":
return schema.Min, nil
case "max":
return schema.Max, nil
case "multipleOf":
return schema.MultipleOf, nil
case "minLength":
return schema.MinLength, nil
case "maxLength":
return schema.MaxLength, nil
case "pattern":
return schema.Pattern, nil
case "minItems":
return schema.MinItems, nil
case "maxItems":
return schema.MaxItems, nil
case "required":
return schema.Required, nil
case "properties":
return schema.Properties, nil
case "minProps":
return schema.MinProps, nil
case "maxProps":
return schema.MaxProps, nil
case "discriminator":
return schema.Discriminator, nil
}
v, _, err := jsonpointer.GetForToken(schema.Extensions, token)
return v, err
}
func (schema *Schema) NewRef() *SchemaRef {
return &SchemaRef{
Value: schema,
}
}
func NewOneOfSchema(schemas ...*Schema) *Schema {
refs := make([]*SchemaRef, 0, len(schemas))
for _, schema := range schemas {
refs = append(refs, &SchemaRef{Value: schema})
}
return &Schema{
OneOf: refs,
}
}
func NewAnyOfSchema(schemas ...*Schema) *Schema {
refs := make([]*SchemaRef, 0, len(schemas))
for _, schema := range schemas {
refs = append(refs, &SchemaRef{Value: schema})
}
return &Schema{
AnyOf: refs,
}
}
func NewAllOfSchema(schemas ...*Schema) *Schema {
refs := make([]*SchemaRef, 0, len(schemas))
for _, schema := range schemas {
refs = append(refs, &SchemaRef{Value: schema})
}
return &Schema{
AllOf: refs,
}
}
func NewBoolSchema() *Schema {
return &Schema{
Type: &Types{TypeBoolean},
}
}
func NewFloat64Schema() *Schema {
return &Schema{
Type: &Types{TypeNumber},
}
}
func NewIntegerSchema() *Schema {
return &Schema{
Type: &Types{TypeInteger},
}
}
func NewInt32Schema() *Schema {
return &Schema{
Type: &Types{TypeInteger},
Format: "int32",
}
}
func NewInt64Schema() *Schema {
return &Schema{
Type: &Types{TypeInteger},
Format: "int64",
}
}
func NewStringSchema() *Schema {
return &Schema{
Type: &Types{TypeString},
}
}
func NewDateTimeSchema() *Schema {
return &Schema{
Type: &Types{TypeString},
Format: "date-time",
}
}
func NewUUIDSchema() *Schema {
return &Schema{
Type: &Types{TypeString},
Format: "uuid",
}
}
func NewBytesSchema() *Schema {
return &Schema{
Type: &Types{TypeString},
Format: "byte",
}
}
func NewArraySchema() *Schema {
return &Schema{
Type: &Types{TypeArray},
}
}
func NewObjectSchema() *Schema {
return &Schema{
Type: &Types{TypeObject},
Properties: make(Schemas),
}
}
func (schema *Schema) WithNullable() *Schema {
schema.Nullable = true
return schema
}
func (schema *Schema) WithMin(value float64) *Schema {
schema.Min = &value
return schema
}
func (schema *Schema) WithMax(value float64) *Schema {
schema.Max = &value
return schema
}
func (schema *Schema) WithExclusiveMin(value bool) *Schema {
schema.ExclusiveMin = value
return schema
}
func (schema *Schema) WithExclusiveMax(value bool) *Schema {
schema.ExclusiveMax = value
return schema
}
func (schema *Schema) WithEnum(values ...interface{}) *Schema {
schema.Enum = values
return schema
}
func (schema *Schema) WithDefault(defaultValue interface{}) *Schema {
schema.Default = defaultValue
return schema
}
func (schema *Schema) WithFormat(value string) *Schema {
schema.Format = value
return schema
}
func (schema *Schema) WithLength(i int64) *Schema {
n := uint64(i)
schema.MinLength = n
schema.MaxLength = &n
return schema
}
func (schema *Schema) WithMinLength(i int64) *Schema {
n := uint64(i)
schema.MinLength = n
return schema
}
func (schema *Schema) WithMaxLength(i int64) *Schema {
n := uint64(i)
schema.MaxLength = &n
return schema
}
func (schema *Schema) WithLengthDecodedBase64(i int64) *Schema {
n := uint64(i)
v := (n*8 + 5) / 6
schema.MinLength = v
schema.MaxLength = &v
return schema
}
func (schema *Schema) WithMinLengthDecodedBase64(i int64) *Schema {
n := uint64(i)
schema.MinLength = (n*8 + 5) / 6
return schema
}
func (schema *Schema) WithMaxLengthDecodedBase64(i int64) *Schema {
n := uint64(i)
schema.MinLength = (n*8 + 5) / 6
return schema
}
func (schema *Schema) WithPattern(pattern string) *Schema {
schema.Pattern = pattern
return schema
}
func (schema *Schema) WithItems(value *Schema) *Schema {
schema.Items = &SchemaRef{
Value: value,
}
return schema
}
func (schema *Schema) WithMinItems(i int64) *Schema {
n := uint64(i)
schema.MinItems = n
return schema
}
func (schema *Schema) WithMaxItems(i int64) *Schema {
n := uint64(i)
schema.MaxItems = &n
return schema
}
func (schema *Schema) WithUniqueItems(unique bool) *Schema {
schema.UniqueItems = unique
return schema
}
func (schema *Schema) WithProperty(name string, propertySchema *Schema) *Schema {
return schema.WithPropertyRef(name, &SchemaRef{
Value: propertySchema,
})
}
func (schema *Schema) WithPropertyRef(name string, ref *SchemaRef) *Schema {
properties := schema.Properties
if properties == nil {
properties = make(Schemas)
schema.Properties = properties
}
properties[name] = ref
return schema
}
func (schema *Schema) WithProperties(properties map[string]*Schema) *Schema {
result := make(Schemas, len(properties))
for k, v := range properties {
result[k] = &SchemaRef{
Value: v,
}
}
schema.Properties = result
return schema
}
func (schema *Schema) WithRequired(required []string) *Schema {
schema.Required = required
return schema
}
func (schema *Schema) WithMinProperties(i int64) *Schema {
n := uint64(i)
schema.MinProps = n
return schema
}
func (schema *Schema) WithMaxProperties(i int64) *Schema {
n := uint64(i)
schema.MaxProps = &n
return schema
}
func (schema *Schema) WithAnyAdditionalProperties() *Schema {
schema.AdditionalProperties = AdditionalProperties{Has: BoolPtr(true)}
return schema
}
func (schema *Schema) WithoutAdditionalProperties() *Schema {
schema.AdditionalProperties = AdditionalProperties{Has: BoolPtr(false)}
return schema
}
func (schema *Schema) WithAdditionalProperties(v *Schema) *Schema {
schema.AdditionalProperties = AdditionalProperties{}
if v != nil {
schema.AdditionalProperties.Schema = &SchemaRef{Value: v}
}
return schema
}
func (schema *Schema) PermitsNull() bool {
return schema.Nullable || schema.Type.Includes("null")
}
// IsEmpty tells whether schema is equivalent to the empty schema `{}`.
func (schema *Schema) IsEmpty() bool {
if schema.Type != nil || schema.Format != "" || len(schema.Enum) != 0 ||
schema.UniqueItems || schema.ExclusiveMin || schema.ExclusiveMax ||
schema.Nullable || schema.ReadOnly || schema.WriteOnly || schema.AllowEmptyValue ||
schema.Min != nil || schema.Max != nil || schema.MultipleOf != nil ||
schema.MinLength != 0 || schema.MaxLength != nil || schema.Pattern != "" ||
schema.MinItems != 0 || schema.MaxItems != nil ||
len(schema.Required) != 0 ||
schema.MinProps != 0 || schema.MaxProps != nil {
return false
}
if n := schema.Not; n != nil && n.Value != nil && !n.Value.IsEmpty() {
return false
}
if ap := schema.AdditionalProperties.Schema; ap != nil && ap.Value != nil && !ap.Value.IsEmpty() {
return false
}
if apa := schema.AdditionalProperties.Has; apa != nil && !*apa {
return false
}
if items := schema.Items; items != nil && items.Value != nil && !items.Value.IsEmpty() {
return false
}
for _, s := range schema.Properties {
if ss := s.Value; ss != nil && !ss.IsEmpty() {
return false
}
}
for _, s := range schema.OneOf {
if ss := s.Value; ss != nil && !ss.IsEmpty() {
return false
}
}
for _, s := range schema.AnyOf {
if ss := s.Value; ss != nil && !ss.IsEmpty() {
return false
}
}
for _, s := range schema.AllOf {
if ss := s.Value; ss != nil && !ss.IsEmpty() {
return false
}
}
return true
}
// Validate returns an error if Schema does not comply with the OpenAPI spec.
func (schema *Schema) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
_, err := schema.validate(ctx, []*Schema{})
return err
}
// returns the updated stack and an error if Schema does not comply with the OpenAPI spec.
func (schema *Schema) validate(ctx context.Context, stack []*Schema) ([]*Schema, error) {
validationOpts := getValidationOptions(ctx)
for _, existing := range stack {
if existing == schema {
return stack, nil
}
}
stack = append(stack, schema)
if schema.ReadOnly && schema.WriteOnly {
return stack, errors.New("a property MUST NOT be marked as both readOnly and writeOnly being true")
}
for _, item := range schema.OneOf {
v := item.Value
if v == nil {
return stack, foundUnresolvedRef(item.Ref)
}
var err error
if stack, err = v.validate(ctx, stack); err != nil {
return stack, err
}
}
for _, item := range schema.AnyOf {
v := item.Value
if v == nil {
return stack, foundUnresolvedRef(item.Ref)
}
var err error
if stack, err = v.validate(ctx, stack); err != nil {
return stack, err
}
}
for _, item := range schema.AllOf {
v := item.Value
if v == nil {
return stack, foundUnresolvedRef(item.Ref)
}
var err error
if stack, err = v.validate(ctx, stack); err != nil {
return stack, err
}
}
if ref := schema.Not; ref != nil {
v := ref.Value
if v == nil {
return stack, foundUnresolvedRef(ref.Ref)
}
var err error
if stack, err = v.validate(ctx, stack); err != nil {
return stack, err
}
}
for _, schemaType := range schema.Type.Slice() {
switch schemaType {
case TypeBoolean:
case TypeNumber:
if format := schema.Format; len(format) > 0 {
switch format {
case "float", "double":
default:
if validationOpts.schemaFormatValidationEnabled {
return stack, unsupportedFormat(format)
}
}
}
case TypeInteger:
if format := schema.Format; len(format) > 0 {
switch format {
case "int32", "int64":
default:
if validationOpts.schemaFormatValidationEnabled {
return stack, unsupportedFormat(format)
}
}
}
case TypeString:
if format := schema.Format; len(format) > 0 {
switch format {
// Supported by OpenAPIv3.0.3:
// https://spec.openapis.org/oas/v3.0.3
case "byte", "binary", "date", "date-time", "password":
// In JSON Draft-07 (not validated yet though):
// https://json-schema.org/draft-07/json-schema-release-notes.html#formats
case "iri", "iri-reference", "uri-template", "idn-email", "idn-hostname":
case "json-pointer", "relative-json-pointer", "regex", "time":
// In JSON Draft 2019-09 (not validated yet though):
// https://json-schema.org/draft/2019-09/release-notes.html#format-vocabulary
case "duration", "uuid":
// Defined in some other specification
case "email", "hostname", "ipv4", "ipv6", "uri", "uri-reference":
default:
// Try to check for custom defined formats
if _, ok := SchemaStringFormats[format]; !ok && validationOpts.schemaFormatValidationEnabled {
return stack, unsupportedFormat(format)
}
}
}
if !validationOpts.schemaPatternValidationDisabled && schema.Pattern != "" {
if _, err := schema.compilePattern(); err != nil {
return stack, err
}
}
case TypeArray:
if schema.Items == nil {
return stack, errors.New("when schema type is 'array', schema 'items' must be non-null")
}
case TypeObject:
default:
return stack, fmt.Errorf("unsupported 'type' value %q", schemaType)
}
}
if ref := schema.Items; ref != nil {
v := ref.Value
if v == nil {
return stack, foundUnresolvedRef(ref.Ref)
}
var err error
if stack, err = v.validate(ctx, stack); err != nil {
return stack, err
}
}
properties := make([]string, 0, len(schema.Properties))
for name := range schema.Properties {
properties = append(properties, name)
}
sort.Strings(properties)
for _, name := range properties {
ref := schema.Properties[name]
v := ref.Value
if v == nil {
return stack, foundUnresolvedRef(ref.Ref)
}
var err error
if stack, err = v.validate(ctx, stack); err != nil {
return stack, err
}
}
if schema.AdditionalProperties.Has != nil && schema.AdditionalProperties.Schema != nil {
return stack, errors.New("additionalProperties are set to both boolean and schema")
}
if ref := schema.AdditionalProperties.Schema; ref != nil {
v := ref.Value
if v == nil {
return stack, foundUnresolvedRef(ref.Ref)
}
var err error
if stack, err = v.validate(ctx, stack); err != nil {
return stack, err
}
}
if v := schema.ExternalDocs; v != nil {
if err := v.Validate(ctx); err != nil {
return stack, fmt.Errorf("invalid external docs: %w", err)
}
}
if v := schema.Default; v != nil && !validationOpts.schemaDefaultsValidationDisabled {
if err := schema.VisitJSON(v); err != nil {
return stack, fmt.Errorf("invalid default: %w", err)
}
}
if x := schema.Example; x != nil && !validationOpts.examplesValidationDisabled {
if err := validateExampleValue(ctx, x, schema); err != nil {
return stack, fmt.Errorf("invalid example: %w", err)
}
}
return stack, validateExtensions(ctx, schema.Extensions)
}
func (schema *Schema) IsMatching(value interface{}) bool {
settings := newSchemaValidationSettings(FailFast())
return schema.visitJSON(settings, value) == nil
}
func (schema *Schema) IsMatchingJSONBoolean(value bool) bool {
settings := newSchemaValidationSettings(FailFast())
return schema.visitJSON(settings, value) == nil
}
func (schema *Schema) IsMatchingJSONNumber(value float64) bool {
settings := newSchemaValidationSettings(FailFast())
return schema.visitJSON(settings, value) == nil
}
func (schema *Schema) IsMatchingJSONString(value string) bool {
settings := newSchemaValidationSettings(FailFast())
return schema.visitJSON(settings, value) == nil
}
func (schema *Schema) IsMatchingJSONArray(value []interface{}) bool {
settings := newSchemaValidationSettings(FailFast())
return schema.visitJSON(settings, value) == nil
}
func (schema *Schema) IsMatchingJSONObject(value map[string]interface{}) bool {
settings := newSchemaValidationSettings(FailFast())
return schema.visitJSON(settings, value) == nil
}
func (schema *Schema) VisitJSON(value interface{}, opts ...SchemaValidationOption) error {
settings := newSchemaValidationSettings(opts...)
return schema.visitJSON(settings, value)
}
func (schema *Schema) visitJSON(settings *schemaValidationSettings, value interface{}) (err error) {
switch value := value.(type) {
case nil:
// Don't use VisitJSONNull, as we still want to reach 'visitXOFOperations', since
// those could allow for a nullable value even though this one doesn't
if schema.PermitsNull() {
return
}
case float64:
if math.IsNaN(value) {
return ErrSchemaInputNaN
}
if math.IsInf(value, 0) {
return ErrSchemaInputInf
}
}
if schema.IsEmpty() {
switch value.(type) {
case nil:
return schema.visitJSONNull(settings)
default:
return
}
}
if err = schema.visitNotOperation(settings, value); err != nil {
return
}
var run bool
if err, run = schema.visitXOFOperations(settings, value); err != nil || !run {
return
}
if err = schema.visitEnumOperation(settings, value); err != nil {
return
}
switch value := value.(type) {
case nil:
return schema.visitJSONNull(settings)
case bool:
return schema.visitJSONBoolean(settings, value)
case json.Number:
valueFloat64, err := value.Float64()
if err != nil {
return &SchemaError{
Value: value,
Schema: schema,
SchemaField: "type",
Reason: "cannot convert json.Number to float64",
customizeMessageError: settings.customizeMessageError,
Origin: err,
}
}
return schema.visitJSONNumber(settings, valueFloat64)
case int:
return schema.visitJSONNumber(settings, float64(value))
case int32:
return schema.visitJSONNumber(settings, float64(value))
case int64:
return schema.visitJSONNumber(settings, float64(value))
case float64:
return schema.visitJSONNumber(settings, value)
case string:
return schema.visitJSONString(settings, value)
case []interface{}:
return schema.visitJSONArray(settings, value)
case map[string]interface{}:
return schema.visitJSONObject(settings, value)
case map[interface{}]interface{}: // for YAML cf. issue #444
values := make(map[string]interface{}, len(value))
for key, v := range value {
if k, ok := key.(string); ok {
values[k] = v
}
}
if len(value) == len(values) {
return schema.visitJSONObject(settings, values)
}
}
// Catch slice of non-empty interface type
if reflect.TypeOf(value).Kind() == reflect.Slice {
valueR := reflect.ValueOf(value)
newValue := make([]interface{}, 0, valueR.Len())
for i := 0; i < valueR.Len(); i++ {
newValue = append(newValue, valueR.Index(i).Interface())
}
return schema.visitJSONArray(settings, newValue)
}
return &SchemaError{
Value: value,
Schema: schema,
SchemaField: "type",
Reason: fmt.Sprintf("unhandled value of type %T", value),
customizeMessageError: settings.customizeMessageError,
}
}
func (schema *Schema) visitEnumOperation(settings *schemaValidationSettings, value interface{}) (err error) {
if enum := schema.Enum; len(enum) != 0 {
for _, v := range enum {
switch c := value.(type) {
case json.Number:
var f float64
if f, err = strconv.ParseFloat(c.String(), 64); err != nil {
return err
}
if v == f {
return
}
case int64:
if v == float64(c) {
return
}
default:
if reflect.DeepEqual(v, value) {
return
}
}
}
if settings.failfast {
return errSchema
}
allowedValues, _ := json.Marshal(enum)
return &SchemaError{
Value: value,
Schema: schema,
SchemaField: "enum",
Reason: fmt.Sprintf("value is not one of the allowed values %s", string(allowedValues)),
customizeMessageError: settings.customizeMessageError,
}
}
return
}
func (schema *Schema) visitNotOperation(settings *schemaValidationSettings, value interface{}) (err error) {
if ref := schema.Not; ref != nil {
v := ref.Value
if v == nil {
return foundUnresolvedRef(ref.Ref)
}
if err := v.visitJSON(settings, value); err == nil {
if settings.failfast {
return errSchema
}
return &SchemaError{
Value: value,
Schema: schema,
SchemaField: "not",
customizeMessageError: settings.customizeMessageError,
}
}
}
return
}
// If the XOF operations pass successfully, abort further run of validation, as they will already be satisfied (unless the schema
// itself is badly specified
func (schema *Schema) visitXOFOperations(settings *schemaValidationSettings, value interface{}) (err error, run bool) {
var visitedOneOf, visitedAnyOf, visitedAllOf bool
if v := schema.OneOf; len(v) > 0 {
var discriminatorRef string
if schema.Discriminator != nil {
pn := schema.Discriminator.PropertyName
if valuemap, okcheck := value.(map[string]interface{}); okcheck {
discriminatorVal, okcheck := valuemap[pn]
if !okcheck {
return &SchemaError{
Schema: schema,
SchemaField: "discriminator",
Reason: fmt.Sprintf("input does not contain the discriminator property %q", pn),
}, false
}
discriminatorValString, okcheck := discriminatorVal.(string)
if !okcheck {
return &SchemaError{
Value: discriminatorVal,
Schema: schema,
SchemaField: "discriminator",
Reason: fmt.Sprintf("value of discriminator property %q is not a string", pn),
}, false
}
if discriminatorRef, okcheck = schema.Discriminator.Mapping[discriminatorValString]; len(schema.Discriminator.Mapping) > 0 && !okcheck {
return &SchemaError{
Value: discriminatorVal,
Schema: schema,
SchemaField: "discriminator",
Reason: fmt.Sprintf("discriminator property %q has invalid value", pn),
}, false
}
}
}
var (
ok = 0
validationErrors = multiErrorForOneOf{}
matchedOneOfIndices = make([]int, 0)
tempValue = value
)
for idx, item := range v {
v := item.Value
if v == nil {
return foundUnresolvedRef(item.Ref), false
}
if discriminatorRef != "" && discriminatorRef != item.Ref {
continue
}
// make a deep copy to protect origin value from being injected default value that defined in mismatched oneOf schema
if settings.asreq || settings.asrep {
tempValue = deepcopy.Copy(value)
}
if err := v.visitJSON(settings, tempValue); err != nil {
validationErrors = append(validationErrors, err)
continue
}
matchedOneOfIndices = append(matchedOneOfIndices, idx)
ok++
}
if ok != 1 {
if settings.failfast {
return errSchema, false
}
e := &SchemaError{
Value: value,
Schema: schema,
SchemaField: "oneOf",
customizeMessageError: settings.customizeMessageError,
}
if ok > 1 {
e.Origin = ErrOneOfConflict
e.Reason = fmt.Sprintf(`value matches more than one schema from "oneOf" (matches schemas at indices %v)`, matchedOneOfIndices)
} else {
e.Origin = fmt.Errorf("doesn't match schema due to: %w", validationErrors)
e.Reason = `value doesn't match any schema from "oneOf"`
}
return e, false
}
// run again to inject default value that defined in matched oneOf schema
if settings.asreq || settings.asrep {
_ = v[matchedOneOfIndices[0]].Value.visitJSON(settings, value)
}
visitedOneOf = true
}
if v := schema.AnyOf; len(v) > 0 {
var (
ok = false
matchedAnyOfIdx = 0
tempValue = value
)
for idx, item := range v {
v := item.Value
if v == nil {
return foundUnresolvedRef(item.Ref), false
}
// make a deep copy to protect origin value from being injected default value that defined in mismatched anyOf schema
if settings.asreq || settings.asrep {
tempValue = deepcopy.Copy(value)
}
if err := v.visitJSON(settings, tempValue); err == nil {
ok = true
matchedAnyOfIdx = idx
break
}
}
if !ok {
if settings.failfast {
return errSchema, false
}
return &SchemaError{
Value: value,
Schema: schema,
SchemaField: "anyOf",
Reason: `doesn't match any schema from "anyOf"`,
customizeMessageError: settings.customizeMessageError,
}, false
}
_ = v[matchedAnyOfIdx].Value.visitJSON(settings, value)
visitedAnyOf = true
}
for _, item := range schema.AllOf {
v := item.Value
if v == nil {
return foundUnresolvedRef(item.Ref), false
}
if err := v.visitJSON(settings, value); err != nil {
if settings.failfast {
return errSchema, false
}
return &SchemaError{
Value: value,
Schema: schema,
SchemaField: "allOf",
Reason: `doesn't match all schemas from "allOf"`,
Origin: err,
customizeMessageError: settings.customizeMessageError,
}, false
}
visitedAllOf = true
}
run = !((visitedOneOf || visitedAnyOf || visitedAllOf) && value == nil)
return
}
// The value is not considered in visitJSONNull because according to the spec
// "null is not supported as a type" unless `nullable` is also set to true
// https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#data-types
// https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#schema-object
func (schema *Schema) visitJSONNull(settings *schemaValidationSettings) (err error) {
if schema.PermitsNull() {
return
}
if settings.failfast {
return errSchema
}
return &SchemaError{
Value: nil,
Schema: schema,
SchemaField: "nullable",
Reason: "Value is not nullable",
customizeMessageError: settings.customizeMessageError,
}
}
func (schema *Schema) VisitJSONBoolean(value bool) error {
settings := newSchemaValidationSettings()
return schema.visitJSONBoolean(settings, value)
}
func (schema *Schema) visitJSONBoolean(settings *schemaValidationSettings, value bool) (err error) {
if !schema.Type.Permits(TypeBoolean) {
return schema.expectedType(settings, value)
}
return
}
func (schema *Schema) VisitJSONNumber(value float64) error {
settings := newSchemaValidationSettings()
return schema.visitJSONNumber(settings, value)
}
func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value float64) error {
var me MultiError
schemaType := schema.Type
requireInteger := false
if schemaType.Permits(TypeInteger) && !schemaType.Permits(TypeNumber) {
requireInteger = true
if bigFloat := big.NewFloat(value); !bigFloat.IsInt() {
if settings.failfast {
return errSchema
}
err := &SchemaError{
Value: value,
Schema: schema,
SchemaField: "type",
Reason: "value must be an integer",
customizeMessageError: settings.customizeMessageError,
}
if !settings.multiError {
return err
}
me = append(me, err)
}
} else if !(schemaType.Permits(TypeInteger) || schemaType.Permits(TypeNumber)) {
return schema.expectedType(settings, value)
}
// formats
if requireInteger && schema.Format != "" {
formatMin := float64(0)
formatMax := float64(0)
switch schema.Format {
case "int32":
formatMin = formatMinInt32
formatMax = formatMaxInt32
case "int64":
formatMin = formatMinInt64
formatMax = formatMaxInt64
default:
if settings.formatValidationEnabled {
return unsupportedFormat(schema.Format)
}
}
if formatMin != 0 && formatMax != 0 && !(formatMin <= value && value <= formatMax) {
if settings.failfast {
return errSchema
}
err := &SchemaError{
Value: value,
Schema: schema,
SchemaField: "format",
Reason: fmt.Sprintf("number must be an %s", schema.Format),
customizeMessageError: settings.customizeMessageError,
}
if !settings.multiError {
return err
}
me = append(me, err)
}
}
// "exclusiveMinimum"
if v := schema.ExclusiveMin; v && !(*schema.Min < value) {
if settings.failfast {
return errSchema
}
err := &SchemaError{
Value: value,
Schema: schema,
SchemaField: "exclusiveMinimum",
Reason: fmt.Sprintf("number must be more than %g", *schema.Min),
customizeMessageError: settings.customizeMessageError,
}
if !settings.multiError {
return err
}
me = append(me, err)
}
// "exclusiveMaximum"
if v := schema.ExclusiveMax; v && !(*schema.Max > value) {
if settings.failfast {
return errSchema
}
err := &SchemaError{
Value: value,
Schema: schema,
SchemaField: "exclusiveMaximum",
Reason: fmt.Sprintf("number must be less than %g", *schema.Max),
customizeMessageError: settings.customizeMessageError,
}
if !settings.multiError {
return err
}
me = append(me, err)
}
// "minimum"
if v := schema.Min; v != nil && !(*v <= value) {
if settings.failfast {
return errSchema
}
err := &SchemaError{
Value: value,
Schema: schema,
SchemaField: "minimum",
Reason: fmt.Sprintf("number must be at least %g", *v),
customizeMessageError: settings.customizeMessageError,
}
if !settings.multiError {
return err
}
me = append(me, err)
}
// "maximum"
if v := schema.Max; v != nil && !(*v >= value) {
if settings.failfast {
return errSchema
}
err := &SchemaError{
Value: value,
Schema: schema,
SchemaField: "maximum",
Reason: fmt.Sprintf("number must be at most %g", *v),
customizeMessageError: settings.customizeMessageError,
}
if !settings.multiError {
return err
}
me = append(me, err)
}
// "multipleOf"
if v := schema.MultipleOf; v != nil {
// "A numeric instance is valid only if division by this keyword's
// value results in an integer."
if bigFloat := big.NewFloat(value / *v); !bigFloat.IsInt() {
if settings.failfast {
return errSchema
}
err := &SchemaError{
Value: value,
Schema: schema,
SchemaField: "multipleOf",
Reason: fmt.Sprintf("number must be a multiple of %g", *v),
customizeMessageError: settings.customizeMessageError,
}
if !settings.multiError {
return err
}
me = append(me, err)
}
}
if len(me) > 0 {
return me
}
return nil
}
func (schema *Schema) VisitJSONString(value string) error {
settings := newSchemaValidationSettings()
return schema.visitJSONString(settings, value)
}
func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value string) error {
if !schema.Type.Permits(TypeString) {
return schema.expectedType(settings, value)
}
var me MultiError
// "minLength" and "maxLength"
minLength := schema.MinLength
maxLength := schema.MaxLength
if minLength != 0 || maxLength != nil {
// JSON schema string lengths are UTF-16, not UTF-8!
length := int64(0)
for _, r := range value {
if utf16.IsSurrogate(r) {
length += 2
} else {
length++
}
}
if minLength != 0 && length < int64(minLength) {
if settings.failfast {
return errSchema
}
err := &SchemaError{
Value: value,
Schema: schema,
SchemaField: "minLength",
Reason: fmt.Sprintf("minimum string length is %d", minLength),
customizeMessageError: settings.customizeMessageError,
}
if !settings.multiError {
return err
}
me = append(me, err)
}
if maxLength != nil && length > int64(*maxLength) {
if settings.failfast {
return errSchema
}
err := &SchemaError{
Value: value,
Schema: schema,
SchemaField: "maxLength",
Reason: fmt.Sprintf("maximum string length is %d", *maxLength),
customizeMessageError: settings.customizeMessageError,
}
if !settings.multiError {
return err
}
me = append(me, err)
}
}
// "pattern"
if !settings.patternValidationDisabled && schema.Pattern != "" {
cpiface, _ := compiledPatterns.Load(schema.Pattern)
cp, _ := cpiface.(*regexp.Regexp)
if cp == nil {
var err error
if cp, err = schema.compilePattern(); err != nil {
if !settings.multiError {
return err
}
me = append(me, err)
}
}
if !cp.MatchString(value) {
err := &SchemaError{
Value: value,
Schema: schema,
SchemaField: "pattern",
Reason: fmt.Sprintf(`string doesn't match the regular expression "%s"`, schema.Pattern),
customizeMessageError: settings.customizeMessageError,
}
if !settings.multiError {
return err
}
me = append(me, err)
}
}
// "format"
var formatStrErr string
var formatErr error
if format := schema.Format; format != "" {
if f, ok := SchemaStringFormats[format]; ok {
switch {
case f.regexp != nil && f.callback == nil:
if cp := f.regexp; !cp.MatchString(value) {
formatStrErr = fmt.Sprintf(`string doesn't match the format %q (regular expression "%s")`, format, cp.String())
}
case f.regexp == nil && f.callback != nil:
if err := f.callback(value); err != nil {
schemaErr := &SchemaError{}
if errors.As(err, &schemaErr) {
formatStrErr = fmt.Sprintf(`string doesn't match the format %q (%s)`, format, schemaErr.Reason)
} else {
formatStrErr = fmt.Sprintf(`string doesn't match the format %q (%v)`, format, err)
}
formatErr = err
}
default:
formatStrErr = fmt.Sprintf("corrupted entry %q in SchemaStringFormats", format)
}
}
}
if formatStrErr != "" || formatErr != nil {
err := &SchemaError{
Value: value,
Schema: schema,
SchemaField: "format",
Reason: formatStrErr,
Origin: formatErr,
customizeMessageError: settings.customizeMessageError,
}
if !settings.multiError {
return err
}
me = append(me, err)
}
if len(me) > 0 {
return me
}
return nil
}
func (schema *Schema) VisitJSONArray(value []interface{}) error {
settings := newSchemaValidationSettings()
return schema.visitJSONArray(settings, value)
}
func (schema *Schema) visitJSONArray(settings *schemaValidationSettings, value []interface{}) error {
if !schema.Type.Permits(TypeArray) {
return schema.expectedType(settings, value)
}
var me MultiError
lenValue := int64(len(value))
// "minItems"
if v := schema.MinItems; v != 0 && lenValue < int64(v) {
if settings.failfast {
return errSchema
}
err := &SchemaError{
Value: value,
Schema: schema,
SchemaField: "minItems",
Reason: fmt.Sprintf("minimum number of items is %d", v),
customizeMessageError: settings.customizeMessageError,
}
if !settings.multiError {
return err
}
me = append(me, err)
}
// "maxItems"
if v := schema.MaxItems; v != nil && lenValue > int64(*v) {
if settings.failfast {
return errSchema
}
err := &SchemaError{
Value: value,
Schema: schema,
SchemaField: "maxItems",
Reason: fmt.Sprintf("maximum number of items is %d", *v),
customizeMessageError: settings.customizeMessageError,
}
if !settings.multiError {
return err
}
me = append(me, err)
}
// "uniqueItems"
if sliceUniqueItemsChecker == nil {
sliceUniqueItemsChecker = isSliceOfUniqueItems
}
if v := schema.UniqueItems; v && !sliceUniqueItemsChecker(value) {
if settings.failfast {
return errSchema
}
err := &SchemaError{
Value: value,
Schema: schema,
SchemaField: "uniqueItems",
Reason: "duplicate items found",
customizeMessageError: settings.customizeMessageError,
}
if !settings.multiError {
return err
}
me = append(me, err)
}
// "items"
if itemSchemaRef := schema.Items; itemSchemaRef != nil {
itemSchema := itemSchemaRef.Value
if itemSchema == nil {
return foundUnresolvedRef(itemSchemaRef.Ref)
}
for i, item := range value {
if err := itemSchema.visitJSON(settings, item); err != nil {
err = markSchemaErrorIndex(err, i)
if !settings.multiError {
return err
}
if itemMe, ok := err.(MultiError); ok {
me = append(me, itemMe...)
} else {
me = append(me, err)
}
}
}
}
if len(me) > 0 {
return me
}
return nil
}
func (schema *Schema) VisitJSONObject(value map[string]interface{}) error {
settings := newSchemaValidationSettings()
return schema.visitJSONObject(settings, value)
}
func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value map[string]interface{}) error {
if !schema.Type.Permits(TypeObject) {
return schema.expectedType(settings, value)
}
var me MultiError
if settings.asreq || settings.asrep {
properties := make([]string, 0, len(schema.Properties))
for propName := range schema.Properties {
properties = append(properties, propName)
}
sort.Strings(properties)
for _, propName := range properties {
propSchema := schema.Properties[propName]
reqRO := settings.asreq && propSchema.Value.ReadOnly && !settings.readOnlyValidationDisabled
repWO := settings.asrep && propSchema.Value.WriteOnly && !settings.writeOnlyValidationDisabled
if f := settings.defaultsSet; f != nil && value[propName] == nil {
if dflt := propSchema.Value.Default; dflt != nil && !reqRO && !repWO {
value[propName] = dflt
settings.onceSettingDefaults.Do(f)
}
}
if value[propName] != nil {
if reqRO {
me = append(me, fmt.Errorf("readOnly property %q in request", propName))
} else if repWO {
me = append(me, fmt.Errorf("writeOnly property %q in response", propName))
}
}
}
}
// "properties"
properties := schema.Properties
lenValue := int64(len(value))
// "minProperties"
if v := schema.MinProps; v != 0 && lenValue < int64(v) {
if settings.failfast {
return errSchema
}
err := &SchemaError{
Value: value,
Schema: schema,
SchemaField: "minProperties",
Reason: fmt.Sprintf("there must be at least %d properties", v),
customizeMessageError: settings.customizeMessageError,
}
if !settings.multiError {
return err
}
me = append(me, err)
}
// "maxProperties"
if v := schema.MaxProps; v != nil && lenValue > int64(*v) {
if settings.failfast {
return errSchema
}
err := &SchemaError{
Value: value,
Schema: schema,
SchemaField: "maxProperties",
Reason: fmt.Sprintf("there must be at most %d properties", *v),
customizeMessageError: settings.customizeMessageError,
}
if !settings.multiError {
return err
}
me = append(me, err)
}
// "additionalProperties"
var additionalProperties *Schema
if ref := schema.AdditionalProperties.Schema; ref != nil {
additionalProperties = ref.Value
}
keys := make([]string, 0, len(value))
for k := range value {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
v := value[k]
if properties != nil {
propertyRef := properties[k]
if propertyRef != nil {
p := propertyRef.Value
if p == nil {
return foundUnresolvedRef(propertyRef.Ref)
}
if err := p.visitJSON(settings, v); err != nil {
if settings.failfast {
return errSchema
}
err = markSchemaErrorKey(err, k)
if !settings.multiError {
return err
}
if v, ok := err.(MultiError); ok {
me = append(me, v...)
continue
}
me = append(me, err)
}
continue
}
}
if allowed := schema.AdditionalProperties.Has; allowed == nil || *allowed {
if additionalProperties != nil {
if err := additionalProperties.visitJSON(settings, v); err != nil {
if settings.failfast {
return errSchema
}
err = markSchemaErrorKey(err, k)
if !settings.multiError {
return err
}
if v, ok := err.(MultiError); ok {
me = append(me, v...)
continue
}
me = append(me, err)
}
}
continue
}
if settings.failfast {
return errSchema
}
err := &SchemaError{
Value: value,
Schema: schema,
SchemaField: "properties",
Reason: fmt.Sprintf("property %q is unsupported", k),
customizeMessageError: settings.customizeMessageError,
}
if !settings.multiError {
return err
}
me = append(me, err)
}
// "required"
for _, k := range schema.Required {
if _, ok := value[k]; !ok {
if s := schema.Properties[k]; s != nil && s.Value.ReadOnly && settings.asreq {
continue
}
if s := schema.Properties[k]; s != nil && s.Value.WriteOnly && settings.asrep {
continue
}
if settings.failfast {
return errSchema
}
err := markSchemaErrorKey(&SchemaError{
Value: value,
Schema: schema,
SchemaField: "required",
Reason: fmt.Sprintf("property %q is missing", k),
customizeMessageError: settings.customizeMessageError,
}, k)
if !settings.multiError {
return err
}
me = append(me, err)
}
}
if len(me) > 0 {
return me
}
return nil
}
func (schema *Schema) expectedType(settings *schemaValidationSettings, value interface{}) error {
if settings.failfast {
return errSchema
}
a := "a"
var x string
schemaTypes := (*schema.Type)
if len(schemaTypes) == 1 {
x = schemaTypes[0]
switch x {
case TypeArray, TypeObject, TypeInteger:
a = "an"
}
} else {
a = "one of"
x = strings.Join(schemaTypes, ", ")
}
return &SchemaError{
Value: value,
Schema: schema,
SchemaField: "type",
Reason: fmt.Sprintf("value must be %s %s", a, x),
customizeMessageError: settings.customizeMessageError,
}
}
// SchemaError is an error that occurs during schema validation.
type SchemaError struct {
// Value is the value that failed validation.
Value interface{}
// reversePath is the path to the value that failed validation.
reversePath []string
// Schema is the schema that failed validation.
Schema *Schema
// SchemaField is the field of the schema that failed validation.
SchemaField string
// Reason is a human-readable message describing the error.
// The message should never include the original value to prevent leakage of potentially sensitive inputs in error messages.
Reason string
// Origin is the original error that caused this error.
Origin error
// customizeMessageError is a function that can be used to customize the error message.
customizeMessageError func(err *SchemaError) string
}
var _ interface{ Unwrap() error } = SchemaError{}
func markSchemaErrorKey(err error, key string) error {
var me multiErrorForOneOf
if errors.As(err, &me) {
err = me.Unwrap()
}
if v, ok := err.(*SchemaError); ok {
v.reversePath = append(v.reversePath, key)
return v
}
if v, ok := err.(MultiError); ok {
for _, e := range v {
_ = markSchemaErrorKey(e, key)
}
return v
}
return err
}
func markSchemaErrorIndex(err error, index int) error {
return markSchemaErrorKey(err, strconv.FormatInt(int64(index), 10))
}
func (err *SchemaError) JSONPointer() []string {
reversePath := err.reversePath
path := append([]string(nil), reversePath...)
for left, right := 0, len(path)-1; left < right; left, right = left+1, right-1 {
path[left], path[right] = path[right], path[left]
}
return path
}
func (err *SchemaError) Error() string {
if err.customizeMessageError != nil {
if msg := err.customizeMessageError(err); msg != "" {
return msg
}
}
buf := bytes.NewBuffer(make([]byte, 0, 256))
if len(err.reversePath) > 0 {
buf.WriteString(`Error at "`)
reversePath := err.reversePath
for i := len(reversePath) - 1; i >= 0; i-- {
buf.WriteByte('/')
buf.WriteString(reversePath[i])
}
buf.WriteString(`": `)
}
if err.Origin != nil {
buf.WriteString(err.Origin.Error())
return buf.String()
}
reason := err.Reason
if reason == "" {
buf.WriteString(`Doesn't match schema "`)
buf.WriteString(err.SchemaField)
buf.WriteString(`"`)
} else {
buf.WriteString(reason)
}
if !SchemaErrorDetailsDisabled {
buf.WriteString("\nSchema:\n ")
encoder := json.NewEncoder(buf)
encoder.SetIndent(" ", " ")
if err := encoder.Encode(err.Schema); err != nil {
panic(err)
}
buf.WriteString("\nValue:\n ")
if err := encoder.Encode(err.Value); err != nil {
panic(err)
}
}
return buf.String()
}
func (err SchemaError) Unwrap() error {
return err.Origin
}
func isSliceOfUniqueItems(xs []interface{}) bool {
s := len(xs)
m := make(map[string]struct{}, s)
for _, x := range xs {
// The input slice is converted from a JSON string, there shall
// have no error when convert it back.
key, _ := json.Marshal(&x)
m[string(key)] = struct{}{}
}
return s == len(m)
}
// SliceUniqueItemsChecker is an function used to check if an given slice
// have unique items.
type SliceUniqueItemsChecker func(items []interface{}) bool
// By default using predefined func isSliceOfUniqueItems which make use of
// json.Marshal to generate a key for map used to check if a given slice
// have unique items.
var sliceUniqueItemsChecker SliceUniqueItemsChecker = isSliceOfUniqueItems
// RegisterArrayUniqueItemsChecker is used to register a customized function
// used to check if JSON array have unique items.
func RegisterArrayUniqueItemsChecker(fn SliceUniqueItemsChecker) {
sliceUniqueItemsChecker = fn
}
func unsupportedFormat(format string) error {
return fmt.Errorf("unsupported 'format' value %q", format)
}
kin-openapi-0.124.0/openapi3/schema_formats.go 0000664 0000000 0000000 00000005476 14604223742 0021150 0 ustar 00root root 0000000 0000000 package openapi3
import (
"fmt"
"net"
"regexp"
"strings"
)
const (
// FormatOfStringForUUIDOfRFC4122 is an optional predefined format for UUID v1-v5 as specified by RFC4122
FormatOfStringForUUIDOfRFC4122 = `^(?:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000)$`
// FormatOfStringForEmail pattern catches only some suspiciously wrong-looking email addresses.
// Use DefineStringFormat(...) if you need something stricter.
FormatOfStringForEmail = `^[^@]+@[^@<>",\s]+$`
)
// FormatCallback performs custom checks on exotic formats
type FormatCallback func(value string) error
// Format represents a format validator registered by either DefineStringFormat or DefineStringFormatCallback
type Format struct {
regexp *regexp.Regexp
callback FormatCallback
}
// SchemaStringFormats allows for validating string formats
var SchemaStringFormats = make(map[string]Format, 4)
// DefineStringFormat defines a new regexp pattern for a given format
func DefineStringFormat(name string, pattern string) {
re, err := regexp.Compile(pattern)
if err != nil {
err := fmt.Errorf("format %q has invalid pattern %q: %w", name, pattern, err)
panic(err)
}
SchemaStringFormats[name] = Format{regexp: re}
}
// DefineStringFormatCallback adds a validation function for a specific schema format entry
func DefineStringFormatCallback(name string, callback FormatCallback) {
SchemaStringFormats[name] = Format{callback: callback}
}
func validateIP(ip string) error {
parsed := net.ParseIP(ip)
if parsed == nil {
return &SchemaError{
Value: ip,
Reason: "Not an IP address",
}
}
return nil
}
func validateIPv4(ip string) error {
if err := validateIP(ip); err != nil {
return err
}
if !(strings.Count(ip, ":") < 2) {
return &SchemaError{
Value: ip,
Reason: "Not an IPv4 address (it's IPv6)",
}
}
return nil
}
func validateIPv6(ip string) error {
if err := validateIP(ip); err != nil {
return err
}
if !(strings.Count(ip, ":") >= 2) {
return &SchemaError{
Value: ip,
Reason: "Not an IPv6 address (it's IPv4)",
}
}
return nil
}
func init() {
// Base64
// The pattern supports base64 and b./ase64url. Padding ('=') is supported.
DefineStringFormat("byte", `(^$|^[a-zA-Z0-9+/\-_]*=*$)`)
// date
DefineStringFormat("date", `^[0-9]{4}-(0[0-9]|10|11|12)-([0-2][0-9]|30|31)$`)
// date-time
DefineStringFormat("date-time", `^[0-9]{4}-(0[0-9]|10|11|12)-([0-2][0-9]|30|31)T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})?$`)
}
// DefineIPv4Format opts in ipv4 format validation on top of OAS 3 spec
func DefineIPv4Format() {
DefineStringFormatCallback("ipv4", validateIPv4)
}
// DefineIPv6Format opts in ipv6 format validation on top of OAS 3 spec
func DefineIPv6Format() {
DefineStringFormatCallback("ipv6", validateIPv6)
}
kin-openapi-0.124.0/openapi3/schema_formats_test.go 0000664 0000000 0000000 00000006633 14604223742 0022203 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"errors"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestIssue430(t *testing.T) {
schema := NewOneOfSchema(
NewStringSchema().WithFormat("ipv4"),
NewStringSchema().WithFormat("ipv6"),
)
delete(SchemaStringFormats, "ipv4")
delete(SchemaStringFormats, "ipv6")
err := schema.Validate(context.Background())
require.NoError(t, err)
data := map[string]bool{
"127.0.1.1": true,
// https://stackoverflow.com/a/48519490/1418165
// v4
"192.168.0.1": true,
// "192.168.0.1:80" doesn't parse per net.ParseIP()
// v6
"::FFFF:C0A8:1": false,
"::FFFF:C0A8:0001": false,
"0000:0000:0000:0000:0000:FFFF:C0A8:1": false,
// "::FFFF:C0A8:1%1" doesn't parse per net.ParseIP()
"::FFFF:192.168.0.1": false,
// "[::FFFF:C0A8:1]:80" doesn't parse per net.ParseIP()
// "[::FFFF:C0A8:1%1]:80" doesn't parse per net.ParseIP()
}
for datum := range data {
err = schema.VisitJSON(datum)
require.Error(t, err, ErrOneOfConflict.Error())
}
DefineIPv4Format()
DefineIPv6Format()
for datum, isV4 := range data {
err = schema.VisitJSON(datum)
require.NoError(t, err)
if isV4 {
require.Nil(t, validateIPv4(datum), "%q should be IPv4", datum)
require.NotNil(t, validateIPv6(datum), "%q should not be IPv6", datum)
} else {
require.NotNil(t, validateIPv4(datum), "%q should not be IPv4", datum)
require.Nil(t, validateIPv6(datum), "%q should be IPv6", datum)
}
}
}
func TestFormatCallback_WrapError(t *testing.T) {
var errSomething = errors.New("something error")
DefineStringFormatCallback("foobar", func(value string) error {
return errSomething
})
s := &Schema{Format: "foobar"}
err := s.VisitJSONString("blablabla")
assert.ErrorIs(t, err, errSomething)
delete(SchemaStringFormats, "foobar")
}
func TestReversePathInMessageSchemaError(t *testing.T) {
DefineIPv4Format()
SchemaErrorDetailsDisabled = true
const spc = `
components:
schemas:
Something:
type: object
properties:
ip:
type: string
format: ipv4
`
l := NewLoader()
doc, err := l.LoadFromData([]byte(spc))
require.NoError(t, err)
err = doc.Components.Schemas["Something"].Value.VisitJSON(map[string]interface{}{
`ip`: `123.0.0.11111`,
})
require.EqualError(t, err, `Error at "/ip": Not an IP address`)
delete(SchemaStringFormats, "ipv4")
SchemaErrorDetailsDisabled = false
}
func TestUuidFormat(t *testing.T) {
type testCase struct {
name string
value string
wantErr bool
}
DefineStringFormat("uuid", FormatOfStringForUUIDOfRFC4122)
testCases := []testCase{
{
name: "invalid",
value: "foo",
wantErr: true,
},
{
name: "uuid v1",
value: "77e66540-ca29-11ed-afa1-0242ac120002",
wantErr: false,
},
{
name: "uuid v4",
value: "00f4d301-b9f4-4366-8907-2b5a03430aa1",
wantErr: false,
},
{
name: "uuid nil",
value: "00000000-0000-0000-0000-000000000000",
wantErr: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := NewUUIDSchema().VisitJSON(tc.value)
var schemaError = &SchemaError{}
if tc.wantErr {
require.Error(t, err)
require.ErrorAs(t, err, &schemaError)
require.NotZero(t, schemaError.Reason)
require.NotContains(t, schemaError.Reason, fmt.Sprint(tc.value))
} else {
require.Nil(t, err)
}
})
}
}
kin-openapi-0.124.0/openapi3/schema_issue289_test.go 0000664 0000000 0000000 00000002205 14604223742 0022112 0 ustar 00root root 0000000 0000000 package openapi3
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestIssue289(t *testing.T) {
spec := []byte(`
components:
schemas:
Server:
properties:
address:
oneOf:
- $ref: "#/components/schemas/ip-address"
- $ref: "#/components/schemas/domain-name"
name:
type: string
type: object
domain-name:
maxLength: 10
minLength: 5
pattern: "((([a-zA-Z0-9_]([a-zA-Z0-9\\-_]){0,61})?[a-zA-Z0-9]\\.)*([a-zA-Z0-9_]([a-zA-Z0-9\\-_]){0,61})?[a-zA-Z0-9]\\.?)|\\."
type: string
ip-address:
pattern: "^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$"
type: string
openapi: "3.0.1"
info:
version: 1.0.0
title: title
paths: {}
`[1:])
loader := NewLoader()
doc, err := loader.LoadFromData(spec)
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
err = doc.Components.Schemas["Server"].Value.VisitJSON(map[string]interface{}{
"name": "kin-openapi",
"address": "127.0.0.1",
})
require.ErrorIs(t, err, ErrOneOfConflict)
}
kin-openapi-0.124.0/openapi3/schema_issue492_test.go 0000664 0000000 0000000 00000002302 14604223742 0022104 0 ustar 00root root 0000000 0000000 package openapi3
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestIssue492(t *testing.T) {
spec := []byte(`
components:
schemas:
Server:
properties:
time:
$ref: "#/components/schemas/timestamp"
name:
type: string
type: object
timestamp:
type: string
format: date-time
openapi: "3.0.1"
paths: {}
info:
version: 1.1.1
title: title
`[1:])
loader := NewLoader()
doc, err := loader.LoadFromData(spec)
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
// verify that the expected format works
err = doc.Components.Schemas["Server"].Value.VisitJSON(map[string]interface{}{
"name": "kin-openapi",
"time": "2001-02-03T04:05:06.789Z",
})
require.NoError(t, err)
// verify that the issue is fixed
err = doc.Components.Schemas["Server"].Value.VisitJSON(map[string]interface{}{
"name": "kin-openapi",
"time": "2001-02-03T04:05:06:789Z",
})
require.ErrorContains(t, err, `Error at "/time": string doesn't match the format "date-time" (regular expression "^[0-9]{4}-(0[0-9]|10|11|12)-([0-2][0-9]|30|31)T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})?$")`)
}
kin-openapi-0.124.0/openapi3/schema_oneOf_test.go 0000664 0000000 0000000 00000011641 14604223742 0021571 0 ustar 00root root 0000000 0000000 package openapi3
import (
"testing"
"github.com/stretchr/testify/require"
)
func oneofSpec(t *testing.T) *T {
t.Helper()
spec := []byte(`
openapi: 3.0.1
paths: {}
info:
version: 1.1.1
title: title
components:
schemas:
Cat:
type: object
properties:
name:
type: string
scratches:
type: boolean
$type:
type: string
enum:
- cat
required:
- name
- scratches
- $type
Dog:
type: object
properties:
name:
type: string
barks:
type: boolean
$type:
type: string
enum:
- dog
required:
- name
- barks
- $type
Animal:
type: object
oneOf:
- $ref: "#/components/schemas/Cat"
- $ref: "#/components/schemas/Dog"
discriminator:
propertyName: $type
mapping:
cat: "#/components/schemas/Cat"
dog: "#/components/schemas/Dog"
`[1:])
loader := NewLoader()
doc, err := loader.LoadFromData(spec)
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
return doc
}
func oneofNoDiscriminatorSpec(t *testing.T) *T {
t.Helper()
spec := []byte(`
openapi: 3.0.1
paths: {}
info:
version: 1.1.1
title: title
components:
schemas:
Cat:
type: object
properties:
name:
type: string
scratches:
type: boolean
required:
- name
- scratches
Dog:
type: object
properties:
name:
type: string
barks:
type: boolean
required:
- name
- barks
Animal:
type: object
oneOf:
- $ref: "#/components/schemas/Cat"
- $ref: "#/components/schemas/Dog"
`[1:])
loader := NewLoader()
doc, err := loader.LoadFromData(spec)
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
return doc
}
func TestVisitJSON_OneOf_MissingDescriptorProperty(t *testing.T) {
doc := oneofSpec(t)
err := doc.Components.Schemas["Animal"].Value.VisitJSON(map[string]interface{}{
"name": "snoopy",
})
require.ErrorContains(t, err, `input does not contain the discriminator property "$type"`)
}
func TestVisitJSON_OneOf_MissingDescriptorValue(t *testing.T) {
doc := oneofSpec(t)
err := doc.Components.Schemas["Animal"].Value.VisitJSON(map[string]interface{}{
"name": "snoopy",
"$type": "snake",
})
require.ErrorContains(t, err, `discriminator property "$type" has invalid value`)
}
func TestVisitJSON_OneOf_MissingField(t *testing.T) {
doc := oneofSpec(t)
err := doc.Components.Schemas["Animal"].Value.VisitJSON(map[string]interface{}{
"name": "snoopy",
"$type": "dog",
})
require.ErrorContains(t, err, `doesn't match schema due to: Error at "/barks": property "barks" is missing`)
}
func TestVisitJSON_OneOf_NoDescriptor_MissingField(t *testing.T) {
doc := oneofNoDiscriminatorSpec(t)
err := doc.Components.Schemas["Animal"].Value.VisitJSON(map[string]interface{}{
"name": "snoopy",
})
require.ErrorContains(t, err, `doesn't match schema due to: Error at "/scratches": property "scratches" is missing`)
}
func TestVisitJSON_OneOf_BadDiscriminatorType(t *testing.T) {
doc := oneofSpec(t)
err := doc.Components.Schemas["Animal"].Value.VisitJSON(map[string]interface{}{
"name": "snoopy",
"scratches": true,
"$type": 1,
})
require.ErrorContains(t, err, `value of discriminator property "$type" is not a string`)
err = doc.Components.Schemas["Animal"].Value.VisitJSON(map[string]interface{}{
"name": "snoopy",
"barks": true,
"$type": nil,
})
require.ErrorContains(t, err, `value of discriminator property "$type" is not a string`)
}
func TestVisitJSON_OneOf_Path(t *testing.T) {
spec := []byte(`
openapi: 3.0.0
paths: {}
info:
version: 1.1.1
title: title
components:
schemas:
Something:
type: object
properties:
first:
type: object
properties:
second:
type: object
properties:
third:
oneOf:
- title: First rule
type: string
minLength: 5
maxLength: 5
- title: Second rule
type: string
minLength: 10
maxLength: 10
`[1:])
loader := NewLoader()
doc, err := loader.LoadFromData(spec)
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
err = doc.Components.Schemas["Something"].Value.VisitJSON(map[string]interface{}{
"first": map[string]interface{}{
"second": map[string]interface{}{
"third": "123456789",
},
},
})
require.ErrorContains(t, err, `Error at "/first/second/third"`)
var sErr *SchemaError
require.ErrorAs(t, err, &sErr)
require.Equal(t, []string{"first", "second", "third"}, sErr.JSONPointer())
}
kin-openapi-0.124.0/openapi3/schema_pattern.go 0000664 0000000 0000000 00000001470 14604223742 0021140 0 ustar 00root root 0000000 0000000 package openapi3
import (
"fmt"
"regexp"
)
var patRewriteCodepoints = regexp.MustCompile(`(?P\\u)(?P[0-9A-F]{4})`)
// See https://pkg.go.dev/regexp/syntax
func intoGoRegexp(re string) string {
return patRewriteCodepoints.ReplaceAllString(re, `\x{${code}}`)
}
// NOTE: racey WRT [writes to schema.Pattern] vs [reads schema.Pattern then writes to compiledPatterns]
func (schema *Schema) compilePattern() (cp *regexp.Regexp, err error) {
pattern := schema.Pattern
if cp, err = regexp.Compile(intoGoRegexp(pattern)); err != nil {
err = &SchemaError{
Schema: schema,
SchemaField: "pattern",
Origin: err,
Reason: fmt.Sprintf("cannot compile pattern %q: %v", pattern, err),
}
return
}
var _ bool = compiledPatterns.CompareAndSwap(pattern, nil, cp)
return
}
kin-openapi-0.124.0/openapi3/schema_pattern_test.go 0000664 0000000 0000000 00000001072 14604223742 0022175 0 ustar 00root root 0000000 0000000 package openapi3
import (
"regexp"
"testing"
"github.com/stretchr/testify/require"
)
func TestPattern(t *testing.T) {
_, err := regexp.Compile("^[a-zA-Z\\u0080-\\u024F\\s\\/\\-\\)\\(\\`\\.\\\"\\']+$")
require.EqualError(t, err, "error parsing regexp: invalid escape sequence: `\\u`")
_, err = regexp.Compile(`^[a-zA-Z\x{0080}-\x{024F}]+$`)
require.NoError(t, err)
require.Equal(t, `^[a-zA-Z\x{0080}-\x{024F}]+$`, intoGoRegexp(`^[a-zA-Z\u0080-\u024F]+$`))
require.Equal(t, `^[6789a-zA-Z\x{0080}-\x{024F}]+$`, intoGoRegexp(`^[6789a-zA-Z\u0080-\u024F]+$`))
}
kin-openapi-0.124.0/openapi3/schema_test.go 0000664 0000000 0000000 00000075026 14604223742 0020452 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"encoding/base64"
"encoding/json"
"math"
"reflect"
"strings"
"testing"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)
type schemaExample struct {
Title string
Schema *Schema
Serialization interface{}
AllValid []interface{}
AllInvalid []interface{}
}
func TestSchemas(t *testing.T) {
DefineStringFormat("uuid", FormatOfStringForUUIDOfRFC4122)
for _, example := range schemaExamples {
t.Run(example.Title, testSchema(t, example))
}
}
func testSchema(t *testing.T, example schemaExample) func(*testing.T) {
return func(t *testing.T) {
schema := example.Schema
if serialized := example.Serialization; serialized != nil {
jsonSerialized, err := json.Marshal(serialized)
require.NoError(t, err)
jsonSchema, err := json.Marshal(schema)
require.NoError(t, err)
require.JSONEq(t, string(jsonSerialized), string(jsonSchema))
var dataUnserialized Schema
err = json.Unmarshal(jsonSerialized, &dataUnserialized)
require.NoError(t, err)
var dataSchema Schema
err = json.Unmarshal(jsonSchema, &dataSchema)
require.NoError(t, err)
require.Equal(t, dataUnserialized, dataSchema)
}
for validateFuncIndex, validateFunc := range validateSchemaFuncs {
for index, value := range example.AllValid {
err := validateFunc(t, schema, value)
require.NoErrorf(t, err, "ValidateFunc #%d, AllValid #%d: %#v", validateFuncIndex, index, value)
}
for index, value := range example.AllInvalid {
err := validateFunc(t, schema, value)
require.Errorf(t, err, "ValidateFunc #%d, AllInvalid #%d: %#v", validateFuncIndex, index, value)
}
}
// NaN and Inf aren't valid JSON but are handled
for index, value := range []interface{}{math.NaN(), math.Inf(-1), math.Inf(+1)} {
err := schema.VisitJSON(value)
require.Errorf(t, err, "NaNAndInf #%d: %#v", index, value)
}
}
}
func validateSchemaJSON(t *testing.T, schema *Schema, value interface{}, opts ...SchemaValidationOption) error {
data, err := json.Marshal(value)
require.NoError(t, err)
var val interface{}
err = json.Unmarshal(data, &val)
require.NoError(t, err)
return schema.VisitJSON(val, opts...)
}
func validateSchemaYAML(t *testing.T, schema *Schema, value interface{}, opts ...SchemaValidationOption) error {
data, err := yaml.Marshal(value)
require.NoError(t, err)
var val interface{}
err = yaml.Unmarshal(data, &val)
require.NoError(t, err)
return schema.VisitJSON(val, opts...)
}
type validateSchemaFunc func(t *testing.T, schema *Schema, value interface{}, opts ...SchemaValidationOption) error
var validateSchemaFuncs = []validateSchemaFunc{
validateSchemaJSON,
validateSchemaYAML,
}
var schemaExamples = []schemaExample{
{
Title: "EMPTY SCHEMA",
Schema: &Schema{},
Serialization: map[string]interface{}{
// This OA3 schema is exactly this draft-04 schema:
// {"not": {"type": "null"}}
},
AllValid: []interface{}{
false,
true,
3.14,
"",
[]interface{}{},
map[string]interface{}{},
},
AllInvalid: []interface{}{
nil,
},
},
{
Title: "JUST NULLABLE",
Schema: NewSchema().WithNullable(),
Serialization: map[string]interface{}{
// This OA3 schema is exactly both this draft-04 schema: {} and:
// {anyOf: [type:string, type:number, type:integer, type:boolean
// ,{type:array, items:{}}, type:object]}
"nullable": true,
},
AllValid: []interface{}{
nil,
false,
true,
0,
0.0,
3.14,
"",
[]interface{}{},
map[string]interface{}{},
},
},
{
Title: "NULLABLE BOOLEAN",
Schema: NewBoolSchema().WithNullable(),
Serialization: map[string]interface{}{
"nullable": true,
"type": "boolean",
},
AllValid: []interface{}{
nil,
false,
true,
},
AllInvalid: []interface{}{
0,
0.0,
3.14,
"",
[]interface{}{},
map[string]interface{}{},
},
},
{
Title: "PRIMITIVES WITHOUT NULL",
Schema: &Schema{
Type: &Types{TypeString, TypeBoolean},
},
AllValid: []interface{}{
"",
"xyz",
true,
false,
},
AllInvalid: []interface{}{
1,
nil,
},
},
{
Title: "PRIMITIVES WITH NULL",
Schema: &Schema{
Type: &Types{TypeNumber, TypeNull},
},
AllValid: []interface{}{
0,
1,
2.3,
nil,
},
AllInvalid: []interface{}{
"x",
[]interface{}{},
},
},
{
Title: "NULLABLE ANYOF",
Schema: NewAnyOfSchema(
NewIntegerSchema(),
NewFloat64Schema(),
).WithNullable(),
Serialization: map[string]interface{}{
"nullable": true,
"anyOf": []interface{}{
map[string]interface{}{"type": "integer"},
map[string]interface{}{"type": "number"},
},
},
AllValid: []interface{}{
nil,
42,
4.2,
},
AllInvalid: []interface{}{
true,
[]interface{}{42},
"bla",
map[string]interface{}{},
},
},
{
Title: "ANYOF NULLABLE CHILD",
Schema: NewAnyOfSchema(
NewIntegerSchema().WithNullable(),
NewFloat64Schema(),
),
Serialization: map[string]interface{}{
"anyOf": []interface{}{
map[string]interface{}{"type": "integer", "nullable": true},
map[string]interface{}{"type": "number"},
},
},
AllValid: []interface{}{
nil,
42,
4.2,
},
AllInvalid: []interface{}{
true,
[]interface{}{42},
"bla",
map[string]interface{}{},
},
},
{
Title: "NULLABLE ALLOF",
Schema: NewAllOfSchema(
NewBoolSchema().WithNullable(),
NewBoolSchema().WithNullable(),
),
Serialization: map[string]interface{}{
"allOf": []interface{}{
map[string]interface{}{"type": "boolean", "nullable": true},
map[string]interface{}{"type": "boolean", "nullable": true},
},
},
AllValid: []interface{}{
nil,
true,
false,
},
AllInvalid: []interface{}{
2,
4.2,
[]interface{}{42},
"bla",
map[string]interface{}{},
},
},
{
Title: "ANYOF WITH PARENT CONSTRAINTS",
Schema: NewAnyOfSchema(
NewObjectSchema().WithRequired([]string{"stringProp"}),
NewObjectSchema().WithRequired([]string{"boolProp"}),
).WithProperties(map[string]*Schema{
"stringProp": NewStringSchema().WithMaxLength(18),
"boolProp": NewBoolSchema(),
}),
Serialization: map[string]interface{}{
"properties": map[string]interface{}{
"stringProp": map[string]interface{}{"type": "string", "maxLength": 18},
"boolProp": map[string]interface{}{"type": "boolean"},
},
"anyOf": []interface{}{
map[string]interface{}{"type": "object", "required": []string{"stringProp"}},
map[string]interface{}{"type": "object", "required": []string{"boolProp"}},
},
},
AllValid: []interface{}{
map[string]interface{}{"stringProp": "valid string value"},
map[string]interface{}{"boolProp": true},
map[string]interface{}{"stringProp": "valid string value", "boolProp": true},
},
AllInvalid: []interface{}{
1,
map[string]interface{}{},
map[string]interface{}{"invalidProp": false},
map[string]interface{}{"stringProp": "invalid string value"},
map[string]interface{}{"stringProp": "invalid string value", "boolProp": true},
},
},
{
Title: "BOOLEAN",
Schema: NewBoolSchema(),
Serialization: map[string]interface{}{
"type": "boolean",
},
AllValid: []interface{}{
false,
true,
},
AllInvalid: []interface{}{
nil,
3.14,
"",
[]interface{}{},
map[string]interface{}{},
},
},
{
Title: "NUMBER",
Schema: NewFloat64Schema().
WithMin(2.5).
WithMax(3.5),
Serialization: map[string]interface{}{
"type": "number",
"minimum": 2.5,
"maximum": 3.5,
},
AllValid: []interface{}{
2.5,
3.14,
3.5,
},
AllInvalid: []interface{}{
nil,
false,
true,
2.4,
3.6,
"",
[]interface{}{},
map[string]interface{}{},
},
},
{
Title: "INTEGER",
Schema: NewInt64Schema().
WithMin(2).
WithMax(5),
Serialization: map[string]interface{}{
"type": "integer",
"format": "int64",
"minimum": 2,
"maximum": 5,
},
AllValid: []interface{}{
2,
5,
},
AllInvalid: []interface{}{
nil,
false,
true,
1,
6,
3.5,
"",
[]interface{}{},
map[string]interface{}{},
},
},
{
Title: "INTEGER OPTIONAL INT64 FORMAT",
Schema: NewInt64Schema(),
Serialization: map[string]interface{}{
"type": "integer",
"format": "int64",
},
AllValid: []interface{}{
1,
256,
65536,
int64(math.MaxInt32) + 10,
int64(math.MinInt32) - 10,
},
AllInvalid: []interface{}{
nil,
false,
3.5,
true,
"",
[]interface{}{},
map[string]interface{}{},
},
},
{
Title: "INTEGER OPTIONAL INT32 FORMAT",
Schema: NewInt32Schema(),
Serialization: map[string]interface{}{
"type": "integer",
"format": "int32",
},
AllValid: []interface{}{
1,
256,
65536,
int64(math.MaxInt32),
int64(math.MaxInt32),
},
AllInvalid: []interface{}{
nil,
false,
3.5,
int64(math.MaxInt32) + 10,
int64(math.MinInt32) - 10,
true,
"",
[]interface{}{},
map[string]interface{}{},
},
},
{
Title: "STRING",
Schema: NewStringSchema().
WithMinLength(2).
WithMaxLength(3).
WithPattern("^[abc]+$"),
Serialization: map[string]interface{}{
"type": "string",
"minLength": 2,
"maxLength": 3,
"pattern": "^[abc]+$",
},
AllValid: []interface{}{
"ab",
"abc",
},
AllInvalid: []interface{}{
nil,
false,
true,
3.14,
"a",
"xy",
"aaaa",
[]interface{}{},
map[string]interface{}{},
},
},
{
Title: "STRING: optional format 'uuid'",
Schema: NewUUIDSchema(),
Serialization: map[string]interface{}{
"type": "string",
"format": "uuid",
},
AllValid: []interface{}{
"dd7d8481-81a3-407f-95f0-a2f1cb382a4b",
"dcba3901-2fba-48c1-9db2-00422055804e",
"ace8e3be-c254-4c10-8859-1401d9a9d52a",
"DD7D8481-81A3-407F-95F0-A2F1CB382A4B",
"DCBA3901-2FBA-48C1-9DB2-00422055804E",
"ACE8E3BE-C254-4C10-8859-1401D9A9D52A",
"dd7D8481-81A3-407f-95F0-A2F1CB382A4B",
"DCBA3901-2FBA-48C1-9db2-00422055804e",
"ACE8E3BE-c254-4C10-8859-1401D9A9D52A",
},
AllInvalid: []interface{}{
nil,
"g39840b1-d0ef-446d-e555-48fcca50a90a",
"4cf3i040-ea14-4daa-b0b5-ea9329473519",
"aaf85740-7e27-4b4f-b4554-a03a43b1f5e3",
"56f5bff4-z4b6-48e6-a10d-b6cf66a83b04",
"G39840B1-D0EF-446D-E555-48FCCA50A90A",
"4CF3I040-EA14-4DAA-B0B5-EA9329473519",
"AAF85740-7E27-4B4F-B4554-A03A43B1F5E3",
"56F5BFF4-Z4B6-48E6-A10D-B6CF66A83B04",
"4CF3I040-EA14-4Daa-B0B5-EA9329473519",
"AAf85740-7E27-4B4F-B4554-A03A43b1F5E3",
"56F5Bff4-Z4B6-48E6-a10D-B6CF66A83B04",
},
},
{
Title: "STRING: format 'date-time'",
Schema: NewDateTimeSchema(),
Serialization: map[string]interface{}{
"type": "string",
"format": "date-time",
},
AllValid: []interface{}{
"2017-12-31T11:59:59",
"2017-12-31T11:59:59Z",
"2017-12-31T11:59:59-11:30",
"2017-12-31T11:59:59+11:30",
"2017-12-31T11:59:59.999+11:30",
"2017-12-31T11:59:59.999Z",
},
AllInvalid: []interface{}{
nil,
3.14,
"2017-12-31",
"2017-12-31T11:59:59\n",
"2017-12-31T11:59:59.+11:30",
"2017-12-31T11:59:59.Z",
},
},
{
Title: "STRING: format 'date-time'",
Schema: NewBytesSchema(),
Serialization: map[string]interface{}{
"type": "string",
"format": "byte",
},
AllValid: []interface{}{
"",
base64.StdEncoding.EncodeToString(func() []byte {
data := make([]byte, 0, 1024)
for i := 0; i < cap(data); i++ {
data = append(data, byte(i))
}
return data
}()),
base64.URLEncoding.EncodeToString(func() []byte {
data := make([]byte, 0, 1024)
for i := 0; i < cap(data); i++ {
data = append(data, byte(i))
}
return data
}()),
},
AllInvalid: []interface{}{
nil,
" ",
"\n\n", // a \n is ok for JSON but not for YAML decoder/encoder
"%",
},
},
{
Title: "ARRAY",
Schema: &Schema{
Type: &Types{"array"},
MinItems: 2,
MaxItems: Uint64Ptr(3),
UniqueItems: true,
Items: NewFloat64Schema().NewRef(),
},
Serialization: map[string]interface{}{
"type": "array",
"minItems": 2,
"maxItems": 3,
"uniqueItems": true,
"items": map[string]interface{}{
"type": "number",
},
},
AllValid: []interface{}{
[]interface{}{
1, 2,
},
[]interface{}{
1, 2, 3,
},
},
AllInvalid: []interface{}{
nil,
3.14,
[]interface{}{
1,
},
[]interface{}{
42, 42,
},
[]interface{}{
1, 2, 3, 4,
},
},
},
{
Title: "ARRAY : items format 'object'",
Schema: &Schema{
Type: &Types{"array"},
UniqueItems: true,
Items: (&Schema{
Type: &Types{"object"},
Properties: Schemas{
"key1": NewFloat64Schema().NewRef(),
},
}).NewRef(),
},
Serialization: map[string]interface{}{
"type": "array",
"uniqueItems": true,
"items": map[string]interface{}{
"properties": map[string]interface{}{
"key1": map[string]interface{}{
"type": "number",
},
},
"type": "object",
},
},
AllValid: []interface{}{
[]interface{}{
map[string]interface{}{
"key1": 1,
"key2": 1,
// Additional properties will make object different
// By default additionalProperties is true
},
map[string]interface{}{
"key1": 1,
},
},
[]interface{}{
map[string]interface{}{
"key1": 1,
},
map[string]interface{}{
"key1": 2,
},
},
},
AllInvalid: []interface{}{
[]interface{}{
map[string]interface{}{
"key1": 1,
},
map[string]interface{}{
"key1": 1,
},
},
},
},
{
Title: "ARRAY : items format 'object' and object with a property of array type ",
Schema: &Schema{
Type: &Types{"array"},
UniqueItems: true,
Items: (&Schema{
Type: &Types{"object"},
Properties: Schemas{
"key1": (&Schema{
Type: &Types{"array"},
UniqueItems: true,
Items: NewFloat64Schema().NewRef(),
}).NewRef(),
},
}).NewRef(),
},
Serialization: map[string]interface{}{
"type": "array",
"uniqueItems": true,
"items": map[string]interface{}{
"properties": map[string]interface{}{
"key1": map[string]interface{}{
"type": "array",
"uniqueItems": true,
"items": map[string]interface{}{
"type": "number",
},
},
},
"type": "object",
},
},
AllValid: []interface{}{
[]interface{}{
map[string]interface{}{
"key1": []interface{}{
1, 2,
},
},
map[string]interface{}{
"key1": []interface{}{
3, 4,
},
},
},
[]interface{}{ // Slice have items with the same value but with different index will treated as different slices
map[string]interface{}{
"key1": []interface{}{
10, 9,
},
},
map[string]interface{}{
"key1": []interface{}{
9, 10,
},
},
},
},
AllInvalid: []interface{}{
[]interface{}{ // Violate outer array uniqueItems: true
map[string]interface{}{
"key1": []interface{}{
9, 9,
},
},
map[string]interface{}{
"key1": []interface{}{
9, 9,
},
},
},
[]interface{}{ // Violate inner(array in object) array uniqueItems: true
map[string]interface{}{
"key1": []interface{}{
9, 9,
},
},
map[string]interface{}{
"key1": []interface{}{
8, 8,
},
},
},
},
},
{
Title: "ARRAY : items format 'array'",
Schema: &Schema{
Type: &Types{"array"},
UniqueItems: true,
Items: (&Schema{
Type: &Types{"array"},
UniqueItems: true,
Items: NewFloat64Schema().NewRef(),
}).NewRef(),
},
Serialization: map[string]interface{}{
"type": "array",
"uniqueItems": true,
"items": map[string]interface{}{
"items": map[string]interface{}{
"type": "number",
},
"uniqueItems": true,
"type": "array",
},
},
AllValid: []interface{}{
[]interface{}{
[]interface{}{1, 2},
[]interface{}{3, 4},
},
[]interface{}{ // Slice have items with the same value but with different index will treated as different slices
[]interface{}{1, 2},
[]interface{}{2, 1},
},
},
AllInvalid: []interface{}{
[]interface{}{ // Violate outer array uniqueItems: true
[]interface{}{8, 9},
[]interface{}{8, 9},
},
[]interface{}{ // Violate inner array uniqueItems: true
[]interface{}{9, 9},
[]interface{}{8, 8},
},
},
},
{
Title: "ARRAY : items format 'array' and array with object type items",
Schema: &Schema{
Type: &Types{"array"},
UniqueItems: true,
Items: (&Schema{
Type: &Types{"array"},
UniqueItems: true,
Items: (&Schema{
Type: &Types{"object"},
Properties: Schemas{
"key1": NewFloat64Schema().NewRef(),
},
}).NewRef(),
}).NewRef(),
},
Serialization: map[string]interface{}{
"type": "array",
"uniqueItems": true,
"items": map[string]interface{}{
"items": map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"key1": map[string]interface{}{
"type": "number",
},
},
},
"uniqueItems": true,
"type": "array",
},
},
AllValid: []interface{}{
[]interface{}{
[]interface{}{
map[string]interface{}{
"key1": 1,
},
},
[]interface{}{
map[string]interface{}{
"key1": 2,
},
},
},
[]interface{}{ // Slice have items with the same value but with different index will treated as different slices
[]interface{}{
map[string]interface{}{
"key1": 1,
},
map[string]interface{}{
"key1": 2,
},
},
[]interface{}{
map[string]interface{}{
"key1": 2,
},
map[string]interface{}{
"key1": 1,
},
},
},
},
AllInvalid: []interface{}{
[]interface{}{ // Violate outer array uniqueItems: true
[]interface{}{
map[string]interface{}{
"key1": 1,
},
map[string]interface{}{
"key1": 2,
},
},
[]interface{}{
map[string]interface{}{
"key1": 1,
},
map[string]interface{}{
"key1": 2,
},
},
},
[]interface{}{ // Violate inner array uniqueItems: true
[]interface{}{
map[string]interface{}{
"key1": 1,
},
map[string]interface{}{
"key1": 1,
},
},
[]interface{}{
map[string]interface{}{
"key1": 2,
},
map[string]interface{}{
"key1": 2,
},
},
},
},
},
{
Title: "OBJECT",
Schema: &Schema{
Type: &Types{"object"},
MaxProps: Uint64Ptr(2),
Properties: Schemas{
"numberProperty": NewFloat64Schema().NewRef(),
},
},
Serialization: map[string]interface{}{
"type": "object",
"maxProperties": 2,
"properties": map[string]interface{}{
"numberProperty": map[string]interface{}{
"type": "number",
},
},
},
AllValid: []interface{}{
map[string]interface{}{},
map[string]interface{}{
"numberProperty": 3.14,
},
map[string]interface{}{
"numberProperty": 3.14,
"some prop": nil,
},
},
AllInvalid: []interface{}{
nil,
false,
true,
3.14,
"",
[]interface{}{},
map[string]interface{}{
"numberProperty": "abc",
},
map[string]interface{}{
"numberProperty": 3.14,
"some prop": 42,
"third": "prop",
},
},
},
{
Schema: &Schema{
Type: &Types{"object"},
AdditionalProperties: AdditionalProperties{Schema: &SchemaRef{
Value: &Schema{
Type: &Types{"number"},
},
}},
},
Serialization: map[string]interface{}{
"type": "object",
"additionalProperties": map[string]interface{}{
"type": "number",
},
},
AllValid: []interface{}{
map[string]interface{}{},
map[string]interface{}{
"x": 3.14,
"y": 3.14,
},
},
AllInvalid: []interface{}{
map[string]interface{}{
"x": "abc",
},
},
},
{
Schema: &Schema{
Type: &Types{"object"},
AdditionalProperties: AdditionalProperties{Has: BoolPtr(true)},
},
Serialization: map[string]interface{}{
"type": "object",
"additionalProperties": true,
},
AllValid: []interface{}{
map[string]interface{}{},
map[string]interface{}{
"x": false,
"y": 3.14,
},
},
},
{
Title: "NOT",
Schema: &Schema{
Not: &SchemaRef{
Value: &Schema{
Enum: []interface{}{
nil,
true,
3.14,
"not this",
},
},
},
},
Serialization: map[string]interface{}{
"not": map[string]interface{}{
"enum": []interface{}{
nil,
true,
3.14,
"not this",
},
},
},
AllValid: []interface{}{
false,
2,
"abc",
},
AllInvalid: []interface{}{
nil,
true,
3.14,
"not this",
},
},
{
Title: "ANY OF",
Schema: &Schema{
AnyOf: []*SchemaRef{
{
Value: NewFloat64Schema().
WithMin(1).
WithMax(2),
},
{
Value: NewFloat64Schema().
WithMin(2).
WithMax(3),
},
},
},
Serialization: map[string]interface{}{
"anyOf": []interface{}{
map[string]interface{}{
"type": "number",
"minimum": 1,
"maximum": 2,
},
map[string]interface{}{
"type": "number",
"minimum": 2,
"maximum": 3,
},
},
},
AllValid: []interface{}{
1,
2,
3,
},
AllInvalid: []interface{}{
0,
4,
},
},
{
Title: "ALL OF",
Schema: &Schema{
AllOf: []*SchemaRef{
{
Value: NewFloat64Schema().
WithMin(1).
WithMax(2),
},
{
Value: NewFloat64Schema().
WithMin(2).
WithMax(3),
},
},
},
Serialization: map[string]interface{}{
"allOf": []interface{}{
map[string]interface{}{
"type": "number",
"minimum": 1,
"maximum": 2,
},
map[string]interface{}{
"type": "number",
"minimum": 2,
"maximum": 3,
},
},
},
AllValid: []interface{}{
2,
},
AllInvalid: []interface{}{
0,
1,
3,
4,
},
},
{
Title: "ONE OF",
Schema: &Schema{
OneOf: []*SchemaRef{
{
Value: NewFloat64Schema().
WithMin(1).
WithMax(2),
},
{
Value: NewFloat64Schema().
WithMin(2).
WithMax(3),
},
},
},
Serialization: map[string]interface{}{
"oneOf": []interface{}{
map[string]interface{}{
"type": "number",
"minimum": 1,
"maximum": 2,
},
map[string]interface{}{
"type": "number",
"minimum": 2,
"maximum": 3,
},
},
},
AllValid: []interface{}{
1,
3,
},
AllInvalid: []interface{}{
0,
2,
4,
},
},
}
type schemaTypeExample struct {
Title string
Schema *Schema
AllValid []string
AllInvalid []string
}
func TestTypes(t *testing.T) {
for _, example := range typeExamples {
t.Run(example.Title, testType(t, example))
}
}
func testType(t *testing.T, example schemaTypeExample) func(*testing.T) {
return func(t *testing.T) {
baseSchema := example.Schema
for _, typ := range example.AllValid {
schema := baseSchema.WithFormat(typ)
err := schema.Validate(context.Background())
require.NoError(t, err)
}
for _, typ := range example.AllInvalid {
schema := baseSchema.WithFormat(typ)
ctx := WithValidationOptions(context.Background(), EnableSchemaFormatValidation())
err := schema.Validate(ctx)
require.Error(t, err)
}
}
}
var typeExamples = []schemaTypeExample{
{
Title: "STRING",
Schema: NewStringSchema(),
AllValid: []string{
"",
"byte",
"binary",
"date",
"date-time",
"password",
// Not supported but allowed:
"uri",
},
AllInvalid: []string{
"code/golang",
},
},
{
Title: "NUMBER",
Schema: NewFloat64Schema(),
AllValid: []string{
"",
"float",
"double",
},
AllInvalid: []string{
"f32",
},
},
{
Title: "INTEGER",
Schema: NewIntegerSchema(),
AllValid: []string{
"",
"int32",
"int64",
},
AllInvalid: []string{
"uint8",
},
},
}
func TestSchemaErrors(t *testing.T) {
for _, example := range schemaErrorExamples {
t.Run(example.Title, testSchemaError(t, example))
}
}
func testSchemaError(t *testing.T, example schemaErrorExample) func(*testing.T) {
return func(t *testing.T) {
msg := example.Error.Error()
require.True(t, strings.Contains(msg, example.Want))
}
}
type schemaErrorExample struct {
Title string
Error *SchemaError
Want string
}
var schemaErrorExamples = []schemaErrorExample{
{
Title: "SIMPLE",
Error: &SchemaError{
Value: 1,
Schema: &Schema{},
Reason: "SIMPLE",
},
Want: "SIMPLE",
},
{
Title: "NEST",
Error: &SchemaError{
Value: 1,
Schema: &Schema{},
Reason: "PARENT",
Origin: &SchemaError{
Value: 1,
Schema: &Schema{},
Reason: "NEST",
},
},
Want: "NEST",
},
}
type schemaMultiErrorExample struct {
Title string
Schema *Schema
Values []interface{}
ExpectedErrors []MultiError
}
func TestSchemasMultiError(t *testing.T) {
for _, example := range schemaMultiErrorExamples {
t.Run(example.Title, testSchemaMultiError(t, example))
}
}
func testSchemaMultiError(t *testing.T, example schemaMultiErrorExample) func(*testing.T) {
return func(t *testing.T) {
schema := example.Schema
for validateFuncIndex, validateFunc := range validateSchemaFuncs {
for i, value := range example.Values {
err := validateFunc(t, schema, value, MultiErrors())
require.Errorf(t, err, "ValidateFunc #%d, value #%d: %#", validateFuncIndex, i, value)
require.IsType(t, MultiError{}, err)
merr, _ := err.(MultiError)
expected := example.ExpectedErrors[i]
require.True(t, len(merr) > 0)
require.Len(t, merr, len(expected))
for _, e := range merr {
require.IsType(t, &SchemaError{}, e)
var found bool
scherr, _ := e.(*SchemaError)
for _, expectedErr := range expected {
expectedScherr, _ := expectedErr.(*SchemaError)
if reflect.DeepEqual(expectedScherr.reversePath, scherr.reversePath) &&
expectedScherr.SchemaField == scherr.SchemaField {
found = true
break
}
}
require.Truef(t, found, "ValidateFunc #%d, value #%d: missing %s error on %s", validateFunc, i, scherr.SchemaField, strings.Join(scherr.JSONPointer(), "."))
}
}
}
}
}
var schemaMultiErrorExamples = []schemaMultiErrorExample{
{
Title: "STRING",
Schema: NewStringSchema().
WithMinLength(2).
WithMaxLength(3).
WithPattern("^[abc]+$"),
Values: []interface{}{
"f",
"foobar",
},
ExpectedErrors: []MultiError{
{&SchemaError{SchemaField: "minLength"}, &SchemaError{SchemaField: "pattern"}},
{&SchemaError{SchemaField: "maxLength"}, &SchemaError{SchemaField: "pattern"}},
},
},
{
Title: "NUMBER",
Schema: NewIntegerSchema().
WithMin(1).
WithMax(10),
Values: []interface{}{
0.5,
10.1,
},
ExpectedErrors: []MultiError{
{&SchemaError{SchemaField: "type"}, &SchemaError{SchemaField: "minimum"}},
{&SchemaError{SchemaField: "type"}, &SchemaError{SchemaField: "maximum"}},
},
},
{
Title: "ARRAY: simple",
Schema: NewArraySchema().
WithMinItems(2).
WithMaxItems(2).
WithItems(NewStringSchema().
WithPattern("^[abc]+$")),
Values: []interface{}{
[]interface{}{"foo"},
[]interface{}{"foo", "bar", "fizz"},
},
ExpectedErrors: []MultiError{
{
&SchemaError{SchemaField: "minItems"},
&SchemaError{SchemaField: "pattern", reversePath: []string{"0"}},
},
{
&SchemaError{SchemaField: "maxItems"},
&SchemaError{SchemaField: "pattern", reversePath: []string{"0"}},
&SchemaError{SchemaField: "pattern", reversePath: []string{"1"}},
&SchemaError{SchemaField: "pattern", reversePath: []string{"2"}},
},
},
},
{
Title: "ARRAY: object",
Schema: NewArraySchema().
WithItems(NewObjectSchema().
WithProperties(map[string]*Schema{
"key1": NewStringSchema(),
"key2": NewIntegerSchema(),
}),
),
Values: []interface{}{
[]interface{}{
map[string]interface{}{
"key1": 100, // not a string
"key2": "not an integer",
},
},
},
ExpectedErrors: []MultiError{
{
&SchemaError{SchemaField: "type", reversePath: []string{"key1", "0"}},
&SchemaError{SchemaField: "type", reversePath: []string{"key2", "0"}},
},
},
},
{
Title: "OBJECT",
Schema: NewObjectSchema().
WithProperties(map[string]*Schema{
"key1": NewStringSchema(),
"key2": NewIntegerSchema(),
"key3": NewArraySchema().
WithItems(NewStringSchema().
WithPattern("^[abc]+$")),
}),
Values: []interface{}{
map[string]interface{}{
"key1": 100, // not a string
"key2": "not an integer",
"key3": []interface{}{"abc", "def"},
},
},
ExpectedErrors: []MultiError{
{
&SchemaError{SchemaField: "type", reversePath: []string{"key1"}},
&SchemaError{SchemaField: "type", reversePath: []string{"key2"}},
&SchemaError{SchemaField: "pattern", reversePath: []string{"1", "key3"}},
},
},
},
}
func TestIssue283(t *testing.T) {
spec := []byte(`
openapi: "3.0.1"
paths: {}
info:
version: 1.1.1
title: title
components:
schemas:
Test:
properties:
name:
type: string
ownerName:
not:
type: boolean
type: object
`[1:])
data := map[string]interface{}{
"name": "kin-openapi",
"ownerName": true,
}
loader := NewLoader()
doc, err := loader.LoadFromData(spec)
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
err = doc.Components.Schemas["Test"].Value.VisitJSON(data)
require.NotNil(t, err)
require.NotEqual(t, errSchema, err)
require.ErrorContains(t, err, `Error at "/ownerName": Doesn't match schema "not"`)
}
func TestValidationFailsOnInvalidPattern(t *testing.T) {
schema := Schema{
Pattern: "[",
Type: &Types{"string"},
}
err := schema.Validate(context.Background())
require.Error(t, err)
}
func TestIssue646(t *testing.T) {
data := []byte(`
enum:
- 42
- []
- [a]
- {}
- {b: c}
`[1:])
var schema Schema
err := yaml.Unmarshal(data, &schema)
require.NoError(t, err)
err = schema.Validate(context.Background())
require.NoError(t, err)
err = schema.VisitJSON(42)
require.NoError(t, err)
err = schema.VisitJSON(1337)
require.Error(t, err)
err = schema.VisitJSON([]interface{}{})
require.NoError(t, err)
err = schema.VisitJSON([]interface{}{"a"})
require.NoError(t, err)
err = schema.VisitJSON([]interface{}{"b"})
require.Error(t, err)
err = schema.VisitJSON(map[string]interface{}{})
require.NoError(t, err)
err = schema.VisitJSON(map[string]interface{}{"b": "c"})
require.NoError(t, err)
err = schema.VisitJSON(map[string]interface{}{"d": "e"})
require.Error(t, err)
}
func TestIssue751(t *testing.T) {
schema := &Schema{
Type: &Types{"array"},
UniqueItems: true,
Items: NewStringSchema().NewRef(),
}
validData := []string{"foo", "bar"}
invalidData := []string{"foo", "foo"}
require.NoError(t, schema.VisitJSON(validData))
require.ErrorContains(t, schema.VisitJSON(invalidData), "duplicate items found")
}
kin-openapi-0.124.0/openapi3/schema_validation_settings.go 0000664 0000000 0000000 00000005736 14604223742 0023546 0 ustar 00root root 0000000 0000000 package openapi3
import (
"sync"
)
// SchemaValidationOption describes options a user has when validating request / response bodies.
type SchemaValidationOption func(*schemaValidationSettings)
type schemaValidationSettings struct {
failfast bool
multiError bool
asreq, asrep bool // exclusive (XOR) fields
formatValidationEnabled bool
patternValidationDisabled bool
readOnlyValidationDisabled bool
writeOnlyValidationDisabled bool
onceSettingDefaults sync.Once
defaultsSet func()
customizeMessageError func(err *SchemaError) string
}
// FailFast returns schema validation errors quicker.
func FailFast() SchemaValidationOption {
return func(s *schemaValidationSettings) { s.failfast = true }
}
func MultiErrors() SchemaValidationOption {
return func(s *schemaValidationSettings) { s.multiError = true }
}
func VisitAsRequest() SchemaValidationOption {
return func(s *schemaValidationSettings) { s.asreq, s.asrep = true, false }
}
func VisitAsResponse() SchemaValidationOption {
return func(s *schemaValidationSettings) { s.asreq, s.asrep = false, true }
}
// EnableFormatValidation setting makes Validate not return an error when validating documents that mention schema formats that are not defined by the OpenAPIv3 specification.
func EnableFormatValidation() SchemaValidationOption {
return func(s *schemaValidationSettings) { s.formatValidationEnabled = true }
}
// DisablePatternValidation setting makes Validate not return an error when validating patterns that are not supported by the Go regexp engine.
func DisablePatternValidation() SchemaValidationOption {
return func(s *schemaValidationSettings) { s.patternValidationDisabled = true }
}
// DisableReadOnlyValidation setting makes Validate not return an error when validating properties marked as read-only
func DisableReadOnlyValidation() SchemaValidationOption {
return func(s *schemaValidationSettings) { s.readOnlyValidationDisabled = true }
}
// DisableWriteOnlyValidation setting makes Validate not return an error when validating properties marked as write-only
func DisableWriteOnlyValidation() SchemaValidationOption {
return func(s *schemaValidationSettings) { s.writeOnlyValidationDisabled = true }
}
// DefaultsSet executes the given callback (once) IFF schema validation set default values.
func DefaultsSet(f func()) SchemaValidationOption {
return func(s *schemaValidationSettings) { s.defaultsSet = f }
}
// SetSchemaErrorMessageCustomizer allows to override the schema error message.
// If the passed function returns an empty string, it returns to the previous Error() implementation.
func SetSchemaErrorMessageCustomizer(f func(err *SchemaError) string) SchemaValidationOption {
return func(s *schemaValidationSettings) { s.customizeMessageError = f }
}
func newSchemaValidationSettings(opts ...SchemaValidationOption) *schemaValidationSettings {
settings := &schemaValidationSettings{}
for _, opt := range opts {
opt(settings)
}
return settings
}
kin-openapi-0.124.0/openapi3/schema_validation_settings_test.go 0000664 0000000 0000000 00000001345 14604223742 0024575 0 ustar 00root root 0000000 0000000 package openapi3_test
import (
"fmt"
"github.com/getkin/kin-openapi/openapi3"
)
func ExampleSetSchemaErrorMessageCustomizer() {
loader := openapi3.NewLoader()
spc := `
components:
schemas:
Something:
type: object
properties:
field:
title: Some field
type: string
`[1:]
doc, err := loader.LoadFromData([]byte(spc))
if err != nil {
panic(err)
}
opt := openapi3.SetSchemaErrorMessageCustomizer(func(err *openapi3.SchemaError) string {
return fmt.Sprintf(`field "%s" should be string`, err.Schema.Title)
})
err = doc.Components.Schemas["Something"].Value.Properties["field"].Value.VisitJSON(123, opt)
fmt.Println(err.Error())
// Output: field "Some field" should be string
}
kin-openapi-0.124.0/openapi3/security_requirements.go 0000664 0000000 0000000 00000002765 14604223742 0022625 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
)
type SecurityRequirements []SecurityRequirement
func NewSecurityRequirements() *SecurityRequirements {
return &SecurityRequirements{}
}
func (srs *SecurityRequirements) With(securityRequirement SecurityRequirement) *SecurityRequirements {
*srs = append(*srs, securityRequirement)
return srs
}
// Validate returns an error if SecurityRequirements does not comply with the OpenAPI spec.
func (srs SecurityRequirements) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
for _, security := range srs {
if err := security.Validate(ctx); err != nil {
return err
}
}
return nil
}
// SecurityRequirement is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-requirement-object
type SecurityRequirement map[string][]string
func NewSecurityRequirement() SecurityRequirement {
return make(SecurityRequirement)
}
func (security SecurityRequirement) Authenticate(provider string, scopes ...string) SecurityRequirement {
if len(scopes) == 0 {
scopes = []string{} // Forces the variable to be encoded as an array instead of null
}
security[provider] = scopes
return security
}
// Validate returns an error if SecurityRequirement does not comply with the OpenAPI spec.
func (security *SecurityRequirement) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
return nil
}
kin-openapi-0.124.0/openapi3/security_requirements_test.go 0000664 0000000 0000000 00000002555 14604223742 0023661 0 ustar 00root root 0000000 0000000 package openapi3
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
)
func TestSecurityRequirementsEncoding(t *testing.T) {
tests := []struct {
requirements *SecurityRequirements
json string
}{
{
requirements: NewSecurityRequirements(),
json: `[]`,
},
{
requirements: NewSecurityRequirements().With(NewSecurityRequirement()),
json: `[{}]`,
},
}
for _, test := range tests {
b, err := json.Marshal(test.requirements)
require.NoError(t, err)
require.Equal(t, test.json, string(b), "incorrect requirements encoding")
}
}
func TestSecurityRequirementEncoding(t *testing.T) {
tests := []struct {
requirement SecurityRequirement
json string
}{
{
requirement: NewSecurityRequirement(),
json: `{}`,
},
{
requirement: NewSecurityRequirement().Authenticate("provider"),
json: `{"provider":[]}`,
},
{
requirement: NewSecurityRequirement().Authenticate("provider", "scope1"),
json: `{"provider":["scope1"]}`,
},
{
requirement: NewSecurityRequirement().Authenticate("provider", "scope1", "scope2"),
json: `{"provider":["scope1","scope2"]}`,
},
}
for _, test := range tests {
b, err := json.Marshal(test.requirement)
require.NoError(t, err)
require.Equal(t, test.json, string(b), "incorrect requirements encoding")
}
}
kin-openapi-0.124.0/openapi3/security_scheme.go 0000664 0000000 0000000 00000027100 14604223742 0021334 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/url"
)
// SecurityScheme is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-scheme-object
type SecurityScheme struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Type string `json:"type,omitempty" yaml:"type,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
In string `json:"in,omitempty" yaml:"in,omitempty"`
Scheme string `json:"scheme,omitempty" yaml:"scheme,omitempty"`
BearerFormat string `json:"bearerFormat,omitempty" yaml:"bearerFormat,omitempty"`
Flows *OAuthFlows `json:"flows,omitempty" yaml:"flows,omitempty"`
OpenIdConnectUrl string `json:"openIdConnectUrl,omitempty" yaml:"openIdConnectUrl,omitempty"`
}
func NewSecurityScheme() *SecurityScheme {
return &SecurityScheme{}
}
func NewCSRFSecurityScheme() *SecurityScheme {
return &SecurityScheme{
Type: "apiKey",
In: "header",
Name: "X-XSRF-TOKEN",
}
}
func NewOIDCSecurityScheme(oidcUrl string) *SecurityScheme {
return &SecurityScheme{
Type: "openIdConnect",
OpenIdConnectUrl: oidcUrl,
}
}
func NewJWTSecurityScheme() *SecurityScheme {
return &SecurityScheme{
Type: "http",
Scheme: "bearer",
BearerFormat: "JWT",
}
}
// MarshalJSON returns the JSON encoding of SecurityScheme.
func (ss SecurityScheme) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{}, 8+len(ss.Extensions))
for k, v := range ss.Extensions {
m[k] = v
}
if x := ss.Type; x != "" {
m["type"] = x
}
if x := ss.Description; x != "" {
m["description"] = x
}
if x := ss.Name; x != "" {
m["name"] = x
}
if x := ss.In; x != "" {
m["in"] = x
}
if x := ss.Scheme; x != "" {
m["scheme"] = x
}
if x := ss.BearerFormat; x != "" {
m["bearerFormat"] = x
}
if x := ss.Flows; x != nil {
m["flows"] = x
}
if x := ss.OpenIdConnectUrl; x != "" {
m["openIdConnectUrl"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets SecurityScheme to a copy of data.
func (ss *SecurityScheme) UnmarshalJSON(data []byte) error {
type SecuritySchemeBis SecurityScheme
var x SecuritySchemeBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "type")
delete(x.Extensions, "description")
delete(x.Extensions, "name")
delete(x.Extensions, "in")
delete(x.Extensions, "scheme")
delete(x.Extensions, "bearerFormat")
delete(x.Extensions, "flows")
delete(x.Extensions, "openIdConnectUrl")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*ss = SecurityScheme(x)
return nil
}
func (ss *SecurityScheme) WithType(value string) *SecurityScheme {
ss.Type = value
return ss
}
func (ss *SecurityScheme) WithDescription(value string) *SecurityScheme {
ss.Description = value
return ss
}
func (ss *SecurityScheme) WithName(value string) *SecurityScheme {
ss.Name = value
return ss
}
func (ss *SecurityScheme) WithIn(value string) *SecurityScheme {
ss.In = value
return ss
}
func (ss *SecurityScheme) WithScheme(value string) *SecurityScheme {
ss.Scheme = value
return ss
}
func (ss *SecurityScheme) WithBearerFormat(value string) *SecurityScheme {
ss.BearerFormat = value
return ss
}
// Validate returns an error if SecurityScheme does not comply with the OpenAPI spec.
func (ss *SecurityScheme) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
hasIn := false
hasBearerFormat := false
hasFlow := false
switch ss.Type {
case "apiKey":
hasIn = true
case "http":
scheme := ss.Scheme
switch scheme {
case "bearer":
hasBearerFormat = true
case "basic", "negotiate", "digest":
default:
return fmt.Errorf("security scheme of type 'http' has invalid 'scheme' value %q", scheme)
}
case "oauth2":
hasFlow = true
case "openIdConnect":
if ss.OpenIdConnectUrl == "" {
return fmt.Errorf("no OIDC URL found for openIdConnect security scheme %q", ss.Name)
}
default:
return fmt.Errorf("security scheme 'type' can't be %q", ss.Type)
}
// Validate "in" and "name"
if hasIn {
switch ss.In {
case "query", "header", "cookie":
default:
return fmt.Errorf("security scheme of type 'apiKey' should have 'in'. It can be 'query', 'header' or 'cookie', not %q", ss.In)
}
if ss.Name == "" {
return errors.New("security scheme of type 'apiKey' should have 'name'")
}
} else if len(ss.In) > 0 {
return fmt.Errorf("security scheme of type %q can't have 'in'", ss.Type)
} else if len(ss.Name) > 0 {
return fmt.Errorf("security scheme of type %q can't have 'name'", ss.Type)
}
// Validate "format"
// "bearerFormat" is an arbitrary string so we only check if the scheme supports it
if !hasBearerFormat && len(ss.BearerFormat) > 0 {
return fmt.Errorf("security scheme of type %q can't have 'bearerFormat'", ss.Type)
}
// Validate "flow"
if hasFlow {
flow := ss.Flows
if flow == nil {
return fmt.Errorf("security scheme of type %q should have 'flows'", ss.Type)
}
if err := flow.Validate(ctx); err != nil {
return fmt.Errorf("security scheme 'flow' is invalid: %w", err)
}
} else if ss.Flows != nil {
return fmt.Errorf("security scheme of type %q can't have 'flows'", ss.Type)
}
return validateExtensions(ctx, ss.Extensions)
}
// OAuthFlows is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauth-flows-object
type OAuthFlows struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Implicit *OAuthFlow `json:"implicit,omitempty" yaml:"implicit,omitempty"`
Password *OAuthFlow `json:"password,omitempty" yaml:"password,omitempty"`
ClientCredentials *OAuthFlow `json:"clientCredentials,omitempty" yaml:"clientCredentials,omitempty"`
AuthorizationCode *OAuthFlow `json:"authorizationCode,omitempty" yaml:"authorizationCode,omitempty"`
}
type oAuthFlowType int
const (
oAuthFlowTypeImplicit oAuthFlowType = iota
oAuthFlowTypePassword
oAuthFlowTypeClientCredentials
oAuthFlowAuthorizationCode
)
// MarshalJSON returns the JSON encoding of OAuthFlows.
func (flows OAuthFlows) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{}, 4+len(flows.Extensions))
for k, v := range flows.Extensions {
m[k] = v
}
if x := flows.Implicit; x != nil {
m["implicit"] = x
}
if x := flows.Password; x != nil {
m["password"] = x
}
if x := flows.ClientCredentials; x != nil {
m["clientCredentials"] = x
}
if x := flows.AuthorizationCode; x != nil {
m["authorizationCode"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets OAuthFlows to a copy of data.
func (flows *OAuthFlows) UnmarshalJSON(data []byte) error {
type OAuthFlowsBis OAuthFlows
var x OAuthFlowsBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "implicit")
delete(x.Extensions, "password")
delete(x.Extensions, "clientCredentials")
delete(x.Extensions, "authorizationCode")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*flows = OAuthFlows(x)
return nil
}
// Validate returns an error if OAuthFlows does not comply with the OpenAPI spec.
func (flows *OAuthFlows) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if v := flows.Implicit; v != nil {
if err := v.validate(ctx, oAuthFlowTypeImplicit, opts...); err != nil {
return fmt.Errorf("the OAuth flow 'implicit' is invalid: %w", err)
}
}
if v := flows.Password; v != nil {
if err := v.validate(ctx, oAuthFlowTypePassword, opts...); err != nil {
return fmt.Errorf("the OAuth flow 'password' is invalid: %w", err)
}
}
if v := flows.ClientCredentials; v != nil {
if err := v.validate(ctx, oAuthFlowTypeClientCredentials, opts...); err != nil {
return fmt.Errorf("the OAuth flow 'clientCredentials' is invalid: %w", err)
}
}
if v := flows.AuthorizationCode; v != nil {
if err := v.validate(ctx, oAuthFlowAuthorizationCode, opts...); err != nil {
return fmt.Errorf("the OAuth flow 'authorizationCode' is invalid: %w", err)
}
}
return validateExtensions(ctx, flows.Extensions)
}
// OAuthFlow is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauth-flow-object
type OAuthFlow struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
AuthorizationURL string `json:"authorizationUrl,omitempty" yaml:"authorizationUrl,omitempty"`
TokenURL string `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"`
RefreshURL string `json:"refreshUrl,omitempty" yaml:"refreshUrl,omitempty"`
Scopes map[string]string `json:"scopes" yaml:"scopes"` // required
}
// MarshalJSON returns the JSON encoding of OAuthFlow.
func (flow OAuthFlow) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{}, 4+len(flow.Extensions))
for k, v := range flow.Extensions {
m[k] = v
}
if x := flow.AuthorizationURL; x != "" {
m["authorizationUrl"] = x
}
if x := flow.TokenURL; x != "" {
m["tokenUrl"] = x
}
if x := flow.RefreshURL; x != "" {
m["refreshUrl"] = x
}
m["scopes"] = flow.Scopes
return json.Marshal(m)
}
// UnmarshalJSON sets OAuthFlow to a copy of data.
func (flow *OAuthFlow) UnmarshalJSON(data []byte) error {
type OAuthFlowBis OAuthFlow
var x OAuthFlowBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "authorizationUrl")
delete(x.Extensions, "tokenUrl")
delete(x.Extensions, "refreshUrl")
delete(x.Extensions, "scopes")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*flow = OAuthFlow(x)
return nil
}
// Validate returns an error if OAuthFlows does not comply with the OpenAPI spec.
func (flow *OAuthFlow) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if v := flow.RefreshURL; v != "" {
if _, err := url.Parse(v); err != nil {
return fmt.Errorf("field 'refreshUrl' is invalid: %w", err)
}
}
if flow.Scopes == nil {
return errors.New("field 'scopes' is missing")
}
return validateExtensions(ctx, flow.Extensions)
}
func (flow *OAuthFlow) validate(ctx context.Context, typ oAuthFlowType, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
typeIn := func(types ...oAuthFlowType) bool {
for _, ty := range types {
if ty == typ {
return true
}
}
return false
}
if in := typeIn(oAuthFlowTypeImplicit, oAuthFlowAuthorizationCode); true {
switch {
case flow.AuthorizationURL == "" && in:
return errors.New("field 'authorizationUrl' is empty or missing")
case flow.AuthorizationURL != "" && !in:
return errors.New("field 'authorizationUrl' should not be set")
case flow.AuthorizationURL != "":
if _, err := url.Parse(flow.AuthorizationURL); err != nil {
return fmt.Errorf("field 'authorizationUrl' is invalid: %w", err)
}
}
}
if in := typeIn(oAuthFlowTypePassword, oAuthFlowTypeClientCredentials, oAuthFlowAuthorizationCode); true {
switch {
case flow.TokenURL == "" && in:
return errors.New("field 'tokenUrl' is empty or missing")
case flow.TokenURL != "" && !in:
return errors.New("field 'tokenUrl' should not be set")
case flow.TokenURL != "":
if _, err := url.Parse(flow.TokenURL); err != nil {
return fmt.Errorf("field 'tokenUrl' is invalid: %w", err)
}
}
}
return flow.Validate(ctx, opts...)
}
kin-openapi-0.124.0/openapi3/security_scheme_test.go 0000664 0000000 0000000 00000007545 14604223742 0022406 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"testing"
"github.com/stretchr/testify/require"
)
type securitySchemeExample struct {
title string
raw []byte
valid bool
}
func TestSecuritySchemaExample(t *testing.T) {
for _, example := range securitySchemeExamples {
t.Run(example.title, func(t *testing.T) {
ss := &SecurityScheme{}
err := ss.UnmarshalJSON(example.raw)
require.NoError(t, err)
err = ss.Validate(context.Background())
if example.valid {
require.NoError(t, err)
} else {
require.Error(t, err)
}
})
}
}
// from https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#fixed-fields-23
var securitySchemeExamples = []securitySchemeExample{
{
title: "Basic Authentication Sample",
raw: []byte(`{
"type": "http",
"scheme": "basic"
}`),
valid: true,
},
{
title: "Negotiate Authentication Sample",
raw: []byte(`{
"type": "http",
"scheme": "negotiate"
}`),
valid: true,
},
{
title: "Unknown http Authentication Sample",
raw: []byte(`{
"type": "http",
"scheme": "notvalid"
}`),
valid: false,
},
{
title: "API Key Sample",
raw: []byte(`{
"type": "apiKey",
"name": "api_key",
"in": "header"
}`),
valid: true,
},
{
title: "apiKey with bearerFormat",
raw: []byte(`{
"type": "apiKey",
"in": "header",
"name": "X-API-KEY",
"bearerFormat": "Arbitrary text"
}`),
valid: false,
},
{
title: "Bearer Sample with arbitrary format",
raw: []byte(`{
"type": "http",
"scheme": "bearer",
"bearerFormat": "Arbitrary text"
}`),
valid: true,
},
{
title: "Implicit OAuth2 Sample",
raw: []byte(`{
"type": "oauth2",
"flows": {
"implicit": {
"authorizationUrl": "https://example.com/api/oauth/dialog",
"scopes": {
"write:pets": "modify pets in your account",
"read:pets": "read your pets"
}
}
}
}`),
valid: true,
},
{
title: "OAuth Flow Object Sample",
raw: []byte(`{
"type": "oauth2",
"flows": {
"implicit": {
"authorizationUrl": "https://example.com/api/oauth/dialog",
"scopes": {
"write:pets": "modify pets in your account",
"read:pets": "read your pets"
}
},
"authorizationCode": {
"authorizationUrl": "https://example.com/api/oauth/dialog",
"tokenUrl": "https://example.com/api/oauth/token",
"scopes": {
"write:pets": "modify pets in your account",
"read:pets": "read your pets"
}
}
}
}`),
valid: true,
},
{
title: "OAuth Flow Object clientCredentials/password",
raw: []byte(`{
"type": "oauth2",
"flows": {
"clientCredentials": {
"tokenUrl": "https://example.com/api/oauth/token",
"scopes": {
"write:pets": "modify pets in your account"
}
},
"password": {
"tokenUrl": "https://example.com/api/oauth/token",
"scopes": {
"read:pets": "read your pets"
}
}
}
}`),
valid: true,
},
{
title: "Invalid Basic",
raw: []byte(`{
"type": "https",
"scheme": "basic"
}`),
valid: false,
},
{
title: "Apikey Cookie",
raw: []byte(`{
"type": "apiKey",
"in": "cookie",
"name": "somecookie"
}`),
valid: true,
},
{
title: "OAuth Flow Object with no scopes",
raw: []byte(`{
"type": "oauth2",
"flows": {
"password": {
"tokenUrl": "https://example.com/api/oauth/token"
}
}
}`),
valid: false,
},
{
title: "OAuth Flow Object with empty scopes",
raw: []byte(`{
"type": "oauth2",
"flows": {
"password": {
"tokenUrl": "https://example.com/api/oauth/token",
"scopes": {}
}
}
}`),
valid: true,
},
{
title: "OIDC Type With URL",
raw: []byte(`{
"type": "openIdConnect",
"openIdConnectUrl": "https://example.com/.well-known/openid-configuration"
}`),
valid: true,
},
{
title: "OIDC Type Without URL",
raw: []byte(`{
"type": "openIdConnect",
"openIdConnectUrl": ""
}`),
valid: false,
},
}
kin-openapi-0.124.0/openapi3/serialization_method.go 0000664 0000000 0000000 00000000746 14604223742 0022365 0 ustar 00root root 0000000 0000000 package openapi3
const (
SerializationSimple = "simple"
SerializationLabel = "label"
SerializationMatrix = "matrix"
SerializationForm = "form"
SerializationSpaceDelimited = "spaceDelimited"
SerializationPipeDelimited = "pipeDelimited"
SerializationDeepObject = "deepObject"
)
// SerializationMethod describes a serialization method of HTTP request's parameters and body.
type SerializationMethod struct {
Style string
Explode bool
}
kin-openapi-0.124.0/openapi3/server.go 0000664 0000000 0000000 00000016343 14604223742 0017456 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"encoding/json"
"errors"
"fmt"
"math"
"net/url"
"sort"
"strings"
)
// Servers is specified by OpenAPI/Swagger standard version 3.
type Servers []*Server
// Validate returns an error if Servers does not comply with the OpenAPI spec.
func (servers Servers) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
for _, v := range servers {
if err := v.Validate(ctx); err != nil {
return err
}
}
return nil
}
// BasePath returns the base path of the first server in the list, or /.
func (servers Servers) BasePath() (string, error) {
for _, server := range servers {
return server.BasePath()
}
return "/", nil
}
func (servers Servers) MatchURL(parsedURL *url.URL) (*Server, []string, string) {
rawURL := parsedURL.String()
if i := strings.IndexByte(rawURL, '?'); i >= 0 {
rawURL = rawURL[:i]
}
for _, server := range servers {
pathParams, remaining, ok := server.MatchRawURL(rawURL)
if ok {
return server, pathParams, remaining
}
}
return nil, nil, ""
}
// Server is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#server-object
type Server struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
URL string `json:"url" yaml:"url"` // Required
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Variables map[string]*ServerVariable `json:"variables,omitempty" yaml:"variables,omitempty"`
}
// BasePath returns the base path extracted from the default values of variables, if any.
// Assumes a valid struct (per Validate()).
func (server *Server) BasePath() (string, error) {
if server == nil {
return "/", nil
}
uri := server.URL
for name, svar := range server.Variables {
uri = strings.ReplaceAll(uri, "{"+name+"}", svar.Default)
}
u, err := url.ParseRequestURI(uri)
if err != nil {
return "", err
}
if bp := u.Path; bp != "" {
return bp, nil
}
return "/", nil
}
// MarshalJSON returns the JSON encoding of Server.
func (server Server) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{}, 3+len(server.Extensions))
for k, v := range server.Extensions {
m[k] = v
}
m["url"] = server.URL
if x := server.Description; x != "" {
m["description"] = x
}
if x := server.Variables; len(x) != 0 {
m["variables"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets Server to a copy of data.
func (server *Server) UnmarshalJSON(data []byte) error {
type ServerBis Server
var x ServerBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "url")
delete(x.Extensions, "description")
delete(x.Extensions, "variables")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*server = Server(x)
return nil
}
func (server Server) ParameterNames() ([]string, error) {
pattern := server.URL
var params []string
for len(pattern) > 0 {
i := strings.IndexByte(pattern, '{')
if i < 0 {
break
}
pattern = pattern[i+1:]
i = strings.IndexByte(pattern, '}')
if i < 0 {
return nil, errors.New("missing '}'")
}
params = append(params, strings.TrimSpace(pattern[:i]))
pattern = pattern[i+1:]
}
return params, nil
}
func (server Server) MatchRawURL(input string) ([]string, string, bool) {
pattern := server.URL
var params []string
for len(pattern) > 0 {
c := pattern[0]
if len(pattern) == 1 && c == '/' {
break
}
if c == '{' {
// Find end of pattern
i := strings.IndexByte(pattern, '}')
if i < 0 {
return nil, "", false
}
pattern = pattern[i+1:]
// Find next matching pattern character or next '/' whichever comes first
np := -1
if len(pattern) > 0 {
np = strings.IndexByte(input, pattern[0])
}
ns := strings.IndexByte(input, '/')
if np < 0 {
i = ns
} else if ns < 0 {
i = np
} else {
i = int(math.Min(float64(np), float64(ns)))
}
if i < 0 {
i = len(input)
}
params = append(params, input[:i])
input = input[i:]
continue
}
if len(input) == 0 || input[0] != c {
return nil, "", false
}
pattern = pattern[1:]
input = input[1:]
}
if input == "" {
input = "/"
}
if input[0] != '/' {
return nil, "", false
}
return params, input, true
}
// Validate returns an error if Server does not comply with the OpenAPI spec.
func (server *Server) Validate(ctx context.Context, opts ...ValidationOption) (err error) {
ctx = WithValidationOptions(ctx, opts...)
if server.URL == "" {
return errors.New("value of url must be a non-empty string")
}
opening, closing := strings.Count(server.URL, "{"), strings.Count(server.URL, "}")
if opening != closing {
return errors.New("server URL has mismatched { and }")
}
if opening != len(server.Variables) {
return errors.New("server has undeclared variables")
}
variables := make([]string, 0, len(server.Variables))
for name := range server.Variables {
variables = append(variables, name)
}
sort.Strings(variables)
for _, name := range variables {
v := server.Variables[name]
if !strings.Contains(server.URL, "{"+name+"}") {
return errors.New("server has undeclared variables")
}
if err = v.Validate(ctx); err != nil {
return
}
}
return validateExtensions(ctx, server.Extensions)
}
// ServerVariable is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#server-variable-object
type ServerVariable struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Enum []string `json:"enum,omitempty" yaml:"enum,omitempty"`
Default string `json:"default,omitempty" yaml:"default,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
}
// MarshalJSON returns the JSON encoding of ServerVariable.
func (serverVariable ServerVariable) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{}, 4+len(serverVariable.Extensions))
for k, v := range serverVariable.Extensions {
m[k] = v
}
if x := serverVariable.Enum; len(x) != 0 {
m["enum"] = x
}
if x := serverVariable.Default; x != "" {
m["default"] = x
}
if x := serverVariable.Description; x != "" {
m["description"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets ServerVariable to a copy of data.
func (serverVariable *ServerVariable) UnmarshalJSON(data []byte) error {
type ServerVariableBis ServerVariable
var x ServerVariableBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "enum")
delete(x.Extensions, "default")
delete(x.Extensions, "description")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*serverVariable = ServerVariable(x)
return nil
}
// Validate returns an error if ServerVariable does not comply with the OpenAPI spec.
func (serverVariable *ServerVariable) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if serverVariable.Default == "" {
data, err := serverVariable.MarshalJSON()
if err != nil {
return err
}
return fmt.Errorf("field default is required in %s", data)
}
return validateExtensions(ctx, serverVariable.Extensions)
}
kin-openapi-0.124.0/openapi3/server_test.go 0000664 0000000 0000000 00000012116 14604223742 0020507 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/require"
)
func TestServerParamNames(t *testing.T) {
server := &Server{
URL: "http://{x}.{y}.example.com",
}
values, err := server.ParameterNames()
require.NoError(t, err)
require.Exactly(t, []string{"x", "y"}, values)
}
func TestServerParamValuesWithPath(t *testing.T) {
server := &Server{
URL: "http://{arg0}.{arg1}.example.com/a/{arg3}-version/{arg4}c{arg5}",
}
for input, expected := range map[string]*serverMatch{
"http://x.example.com/a/b": nil,
"http://x.y.example.com/": nil,
"http://x.y.example.com/a/": nil,
"http://x.y.example.com/a/c": nil,
"http://baddomain.com/.example.com/a/1.0.0-version/c/d": nil,
"http://baddomain.com/.example.com/a/1.0.0/2/2.0.0-version/c": nil,
"http://x.y.example.com/a/b-version/prefixedc": newServerMatch("/", "x", "y", "b", "prefixed", ""),
"http://x.y.example.com/a/b-version/c": newServerMatch("/", "x", "y", "b", "", ""),
"http://x.y.example.com/a/b-version/c/": newServerMatch("/", "x", "y", "b", "", ""),
"http://x.y.example.com/a/b-version/c/d": newServerMatch("/d", "x", "y", "b", "", ""),
"http://domain0.domain1.example.com/a/b-version/c/d": newServerMatch("/d", "domain0", "domain1", "b", "", ""),
"http://domain0.domain1.example.com/a/1.0.0-version/c/d": newServerMatch("/d", "domain0", "domain1", "1.0.0", "", ""),
} {
t.Run(input, testServerParamValues(t, server, input, expected))
}
}
func TestServerParamValuesNoPath(t *testing.T) {
server := &Server{
URL: "https://{arg0}.{arg1}.example.com/",
}
for input, expected := range map[string]*serverMatch{
"https://domain0.domain1.example.com/": newServerMatch("/", "domain0", "domain1"),
} {
t.Run(input, testServerParamValues(t, server, input, expected))
}
}
func validServer() *Server {
return &Server{
URL: "http://my.cool.website",
}
}
func invalidServer() *Server {
return &Server{}
}
func TestServerValidation(t *testing.T) {
tests := []struct {
name string
input *Server
expectedError error
}{
{
"when no URL is provided",
invalidServer(),
errors.New("value of url must be a non-empty string"),
},
{
"when a URL is provided",
validServer(),
nil,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
c := context.Background()
validationErr := test.input.Validate(c)
require.Equal(t, test.expectedError, validationErr, "expected errors (or lack of) to match")
})
}
}
func testServerParamValues(t *testing.T, server *Server, input string, expected *serverMatch) func(*testing.T) {
return func(t *testing.T) {
args, remaining, ok := server.MatchRawURL(input)
if expected == nil {
require.False(t, ok)
return
}
require.True(t, ok)
actual := &serverMatch{
Remaining: remaining,
Args: args,
}
require.Equal(t, expected, actual)
}
}
type serverMatch struct {
Remaining string
Args []string
}
func newServerMatch(remaining string, args ...string) *serverMatch {
return &serverMatch{
Remaining: remaining,
Args: args,
}
}
func TestServersBasePath(t *testing.T) {
for _, testcase := range []struct {
title string
servers Servers
expected string
}{
{
title: "empty servers",
servers: nil,
expected: "/",
},
{
title: "URL set, missing trailing slash",
servers: Servers{&Server{URL: "https://example.com"}},
expected: "/",
},
{
title: "URL set, with trailing slash",
servers: Servers{&Server{URL: "https://example.com/"}},
expected: "/",
},
{
title: "URL set",
servers: Servers{&Server{URL: "https://example.com/b/l/a"}},
expected: "/b/l/a",
},
{
title: "URL set with variables",
servers: Servers{&Server{
URL: "{scheme}://example.com/b/l/a",
Variables: map[string]*ServerVariable{
"scheme": {
Enum: []string{"http", "https"},
Default: "https",
},
},
}},
expected: "/b/l/a",
},
{
title: "URL set with variables in path",
servers: Servers{&Server{
URL: "http://example.com/b/{var1}/a",
Variables: map[string]*ServerVariable{
"var1": {
Default: "lllll",
},
},
}},
expected: "/b/lllll/a",
},
{
title: "URLs set with variables in path",
servers: Servers{
&Server{
URL: "http://example.com/b/{var2}/a",
Variables: map[string]*ServerVariable{
"var2": {
Default: "LLLLL",
},
},
},
&Server{
URL: "https://example.com/b/{var1}/a",
Variables: map[string]*ServerVariable{
"var1": {
Default: "lllll",
},
},
},
},
expected: "/b/LLLLL/a",
},
} {
t.Run(testcase.title, func(t *testing.T) {
err := testcase.servers.Validate(context.Background())
require.NoError(t, err)
got, err := testcase.servers.BasePath()
require.NoError(t, err)
require.Exactly(t, testcase.expected, got)
})
}
}
kin-openapi-0.124.0/openapi3/tag.go 0000664 0000000 0000000 00000004322 14604223742 0016715 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"encoding/json"
"fmt"
)
// Tags is specified by OpenAPI/Swagger 3.0 standard.
type Tags []*Tag
func (tags Tags) Get(name string) *Tag {
for _, tag := range tags {
if tag.Name == name {
return tag
}
}
return nil
}
// Validate returns an error if Tags does not comply with the OpenAPI spec.
func (tags Tags) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
for _, v := range tags {
if err := v.Validate(ctx); err != nil {
return err
}
}
return nil
}
// Tag is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#tag-object
type Tag struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
}
// MarshalJSON returns the JSON encoding of Tag.
func (t Tag) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{}, 3+len(t.Extensions))
for k, v := range t.Extensions {
m[k] = v
}
if x := t.Name; x != "" {
m["name"] = x
}
if x := t.Description; x != "" {
m["description"] = x
}
if x := t.ExternalDocs; x != nil {
m["externalDocs"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets Tag to a copy of data.
func (t *Tag) UnmarshalJSON(data []byte) error {
type TagBis Tag
var x TagBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "name")
delete(x.Extensions, "description")
delete(x.Extensions, "externalDocs")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*t = Tag(x)
return nil
}
// Validate returns an error if Tag does not comply with the OpenAPI spec.
func (t *Tag) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if v := t.ExternalDocs; v != nil {
if err := v.Validate(ctx); err != nil {
return fmt.Errorf("invalid external docs: %w", err)
}
}
return validateExtensions(ctx, t.Extensions)
}
kin-openapi-0.124.0/openapi3/testdata/ 0000775 0000000 0000000 00000000000 14604223742 0017423 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/303bis/ 0000775 0000000 0000000 00000000000 14604223742 0020426 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/303bis/common/ 0000775 0000000 0000000 00000000000 14604223742 0021716 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/303bis/common/properties.yaml 0000664 0000000 0000000 00000000467 14604223742 0025005 0 ustar 00root root 0000000 0000000 timestamp:
type: string
description: Date and time in ISO 8601 format.
example: "2020-04-09T18:14:30Z"
readOnly: true
nullable: true
timestamps:
type: object
properties:
created_at:
$ref: "#/timestamp"
deleted_at:
$ref: "#/timestamp"
updated_at:
$ref: "#/timestamp"
kin-openapi-0.124.0/openapi3/testdata/303bis/service.yaml 0000664 0000000 0000000 00000001105 14604223742 0022747 0 ustar 00root root 0000000 0000000 openapi: 3.0.0
info:
title: 'some service spec'
version: 1.2.3
paths:
/service:
get:
tags:
- services/service
summary: List services
description: List services.
operationId: list-services
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/model_service"
components:
schemas:
model_service:
allOf:
- $ref: "common/properties.yaml#/timestamps"
kin-openapi-0.124.0/openapi3/testdata/Test_param_override.yml 0000664 0000000 0000000 00000001577 14604223742 0024156 0 ustar 00root root 0000000 0000000 openapi: 3.0.0
info:
title: customer
version: '1.0'
servers:
- url: 'httpbin.kwaf-demo.test'
paths:
'/customers/{customer_id}':
parameters:
- schema:
type: integer
name: customer_id
in: path
required: true
get:
parameters:
- schema:
type: integer
maximum: 100
name: customer_id
in: path
required: true
summary: customer
tags: []
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
customer_id:
type: integer
customer_name:
type: string
operationId: get-customers-customer_id
description: Retrieve a specific customer by ID
components:
schemas: {}
kin-openapi-0.124.0/openapi3/testdata/callback-transactioned.yml 0000664 0000000 0000000 00000000371 14604223742 0024537 0 ustar 00root root 0000000 0000000 post:
requestBody:
description: Callback payload
content:
'application/json':
schema:
$ref: 'callbacks.yml#/components/schemas/SomePayload'
responses:
'200':
description: callback successfully processed
kin-openapi-0.124.0/openapi3/testdata/callbacks.yml 0000664 0000000 0000000 00000003301 14604223742 0022062 0 ustar 00root root 0000000 0000000 openapi: 3.1.0
info:
title: Callback refd
version: 1.2.3
paths:
/trans:
post:
description: ''
requestBody:
description: ''
content:
'application/json':
schema:
properties:
id: {type: string}
email: {format: email}
responses:
'201':
description: subscription successfully created
content:
application/json:
schema:
type: object
callbacks:
transactionCallback:
'http://notificationServer.com?transactionId={$request.body#/id}&email={$request.body#/email}':
$ref: callback-transactioned.yml
/other:
post:
description: ''
parameters:
- name: queryUrl
in: query
required: true
description: |
bla
bla
bla
schema:
type: string
format: uri
example: https://example.com
responses:
'201':
description: ''
content:
application/json:
schema:
type: object
callbacks:
myEvent:
$ref: '#/components/callbacks/MyCallbackEvent'
components:
schemas:
SomePayload: {type: object}
SomeOtherPayload: {type: boolean}
callbacks:
MyCallbackEvent:
'{$request.query.queryUrl}':
post:
requestBody:
description: Callback payload
content:
'application/json':
schema:
$ref: '#/components/schemas/SomeOtherPayload'
responses:
'200':
description: callback successfully processed
kin-openapi-0.124.0/openapi3/testdata/callbacks.yml.internalized.yml 0000664 0000000 0000000 00000006110 14604223742 0025352 0 ustar 00root root 0000000 0000000 {
"components": {
"callbacks": {
"MyCallbackEvent": {
"{$request.query.queryUrl}": {
"post": {
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SomeOtherPayload"
}
}
},
"description": "Callback payload"
},
"responses": {
"200": {
"description": "callback successfully processed"
}
}
}
}
}
},
"schemas": {
"SomeOtherPayload": {
"type": "boolean"
},
"SomePayload": {
"type": "object"
}
}
},
"info": {
"title": "Callback refd",
"version": "1.2.3"
},
"openapi": "3.1.0",
"paths": {
"/other": {
"post": {
"callbacks": {
"myEvent": {
"$ref": "#/components/callbacks/MyCallbackEvent"
}
},
"parameters": [
{
"description": "bla\nbla\nbla\n",
"in": "query",
"name": "queryUrl",
"required": true,
"schema": {
"example": "https://example.com",
"format": "uri",
"type": "string"
}
}
],
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": ""
}
}
}
},
"/trans": {
"post": {
"callbacks": {
"transactionCallback": {
"http://notificationServer.com?transactionId={$request.body#/id}&email={$request.body#/email}": {
"post": {
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SomePayload"
}
}
},
"description": "Callback payload"
},
"responses": {
"200": {
"description": "callback successfully processed"
}
}
}
}
}
},
"requestBody": {
"content": {
"application/json": {
"schema": {
"properties": {
"email": {
"format": "email"
},
"id": {
"type": "string"
}
}
}
}
}
},
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": "subscription successfully created"
}
}
}
}
}
}
kin-openapi-0.124.0/openapi3/testdata/circularRef/ 0000775 0000000 0000000 00000000000 14604223742 0021664 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/circularRef/base.yml 0000664 0000000 0000000 00000000517 14604223742 0023324 0 ustar 00root root 0000000 0000000 openapi: "3.0.3"
info:
title: Recursive cyclic refs example
version: "1.0"
components:
schemas:
Foo:
properties:
foo2:
$ref: "other.yml#/components/schemas/Foo2"
bar:
$ref: "#/components/schemas/Bar"
Bar:
properties:
foo:
$ref: "#/components/schemas/Foo"
kin-openapi-0.124.0/openapi3/testdata/circularRef/other.yml 0000664 0000000 0000000 00000000245 14604223742 0023531 0 ustar 00root root 0000000 0000000 openapi: "3.0.3"
info:
title: Recursive cyclic refs example
version: "1.0"
components:
schemas:
Foo2:
properties:
id:
type: string
kin-openapi-0.124.0/openapi3/testdata/circularref.openapi.yml 0000664 0000000 0000000 00000000573 14604223742 0024106 0 ustar 00root root 0000000 0000000 ---
openapi: 3.0.0
info:
title: 'OAI Specification in YAML'
version: 0.0.1
paths:
/test:
get:
responses:
"200":
$ref: '#/components/responses/GetTestOK'
components:
responses:
GetTestOK:
description: OK
content:
application/json:
schema:
$ref: 'pathref.openapi.yml#/components/schemas/TestSchema'
kin-openapi-0.124.0/openapi3/testdata/components.openapi.json 0000664 0000000 0000000 00000003262 14604223742 0024140 0 ustar 00root root 0000000 0000000 {
"openapi": "3.0.0",
"info": {
"title": "",
"version": "1"
},
"paths": {},
"components": {
"schemas": {
"Name": {
"type": "string"
},
"CustomTestSchema": {
"$ref": "#/components/schemas/Name"
}
},
"responses": {
"Name": {
"description": "description"
},
"CustomTestResponse": {
"$ref": "#/components/responses/Name"
}
},
"parameters": {
"Name": {
"name": "id",
"in": "header"
},
"CustomTestParameter": {
"$ref": "#/components/parameters/Name"
}
},
"examples": {
"Name": {
"description": "description"
},
"CustomTestExample": {
"$ref": "#/components/examples/Name"
}
},
"requestBodies": {
"Name": {
"content": {}
},
"CustomTestRequestBody": {
"$ref": "#/components/requestBodies/Name"
}
},
"headers": {
"Name": {
"description": "description"
},
"CustomTestHeader": {
"$ref": "#/components/headers/Name"
}
},
"securitySchemes": {
"Name": {
"type": "cookie",
"description": "description"
},
"CustomTestSecurityScheme": {
"$ref": "#/components/securitySchemes/Name"
}
}
}
} kin-openapi-0.124.0/openapi3/testdata/components.openapi.yml 0000664 0000000 0000000 00000001654 14604223742 0023773 0 ustar 00root root 0000000 0000000 ---
openapi: 3.0.0
info:
title: ''
version: '1'
paths: {}
components:
schemas:
Name:
type: string
CustomTestSchema:
"$ref": "#/components/schemas/Name"
responses:
Name:
description: description
CustomTestResponse:
"$ref": "#/components/responses/Name"
parameters:
Name:
name: id
in: header
CustomTestParameter:
"$ref": "#/components/parameters/Name"
examples:
Name:
description: description
CustomTestExample:
"$ref": "#/components/examples/Name"
requestBodies:
Name:
content: {}
CustomTestRequestBody:
"$ref": "#/components/requestBodies/Name"
headers:
Name:
description: description
CustomTestHeader:
"$ref": "#/components/headers/Name"
securitySchemes:
Name:
type: cookie
description: description
CustomTestSecurityScheme:
"$ref": "#/components/securitySchemes/Name"
kin-openapi-0.124.0/openapi3/testdata/draft04.yml 0000664 0000000 0000000 00000005154 14604223742 0021417 0 ustar 00root root 0000000 0000000 id: http://json-schema.org/draft-04/schema#
$schema: http://json-schema.org/draft-04/schema#
description: Core schema meta-schema
definitions:
schemaArray:
type: array
minItems: 1
items:
$ref: '#'
positiveInteger:
type: integer
minimum: 0
positiveIntegerDefault0:
allOf:
- $ref: '#/definitions/positiveInteger'
- default: 0
simpleTypes:
enum:
- array
- boolean
- integer
- 'null'
- number
- object
- string
stringArray:
type: array
items:
type: string
minItems: 1
uniqueItems: true
type: object
properties:
id:
type: string
$schema:
type: string
title:
type: string
description:
type: string
default: {}
multipleOf:
type: number
minimum: 0
exclusiveMinimum: true
maximum:
type: number
exclusiveMaximum:
type: boolean
default: false
minimum:
type: number
exclusiveMinimum:
type: boolean
default: false
maxLength:
$ref: '#/definitions/positiveInteger'
minLength:
$ref: '#/definitions/positiveIntegerDefault0'
pattern:
type: string
format: regex
additionalItems:
anyOf:
- type: boolean
- $ref: '#'
default: {}
items:
anyOf:
- $ref: '#'
- $ref: '#/definitions/schemaArray'
default: {}
maxItems:
$ref: '#/definitions/positiveInteger'
minItems:
$ref: '#/definitions/positiveIntegerDefault0'
uniqueItems:
type: boolean
default: false
maxProperties:
$ref: '#/definitions/positiveInteger'
minProperties:
$ref: '#/definitions/positiveIntegerDefault0'
required:
$ref: '#/definitions/stringArray'
additionalProperties:
anyOf:
- type: boolean
- $ref: '#'
default: {}
definitions:
type: object
additionalProperties:
$ref: '#'
default: {}
properties:
type: object
additionalProperties:
$ref: '#'
default: {}
patternProperties:
type: object
additionalProperties:
'$ref': '#'
default: {}
dependencies:
type: object
additionalProperties:
anyOf:
- $ref: '#'
- $ref: '#/definitions/stringArray'
enum:
type: array
minItems: 1
uniqueItems: true
type:
anyOf:
- $ref: '#/definitions/simpleTypes'
- type: array
items:
$ref: '#/definitions/simpleTypes'
minItems: 1
uniqueItems: true
format:
type: string
allOf:
$ref: '#/definitions/schemaArray'
anyOf:
$ref: '#/definitions/schemaArray'
oneOf:
$ref: '#/definitions/schemaArray'
not:
$ref: '#'
dependencies:
exclusiveMaximum:
- maximum
exclusiveMinimum:
- minimum
default: {}
kin-openapi-0.124.0/openapi3/testdata/ext.json 0000664 0000000 0000000 00000000602 14604223742 0021114 0 ustar 00root root 0000000 0000000 {
"$schema": "http://json-schema.org/draft-04/schema#",
"definitions": {
"a": {
"type": "string"
},
"b": {
"type": "object",
"description": "I use a local reference.",
"properties": {
"name": {
"$ref": "#/definitions/a"
}
}
}
}
}
kin-openapi-0.124.0/openapi3/testdata/issue235.spec0-typo.yml 0000664 0000000 0000000 00000000766 14604223742 0023543 0 ustar 00root root 0000000 0000000 openapi: 3.0.0
info:
title: 'OAI Specification in YAML'
version: 0.0.1
paths:
/test:
get:
responses:
"200":
$ref: '#/components/responses/GetTestOK'
components:
responses:
GetTestOK:
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/ObjectA'
schemas:
ObjectA:
type: object
properties:
object_b:
$ref: 'issue235.spec0-typo.yml#/components/schemas/ObjectD'
kin-openapi-0.124.0/openapi3/testdata/issue235.spec0.yml 0000664 0000000 0000000 00000000761 14604223742 0022545 0 ustar 00root root 0000000 0000000 openapi: 3.0.0
info:
title: 'OAI Specification in YAML'
version: 0.0.1
paths:
/test:
get:
responses:
"200":
$ref: '#/components/responses/GetTestOK'
components:
responses:
GetTestOK:
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/ObjectA'
schemas:
ObjectA:
type: object
properties:
object_b:
$ref: 'issue235.spec1.yml#/components/schemas/ObjectD'
kin-openapi-0.124.0/openapi3/testdata/issue235.spec1.yml 0000664 0000000 0000000 00000000365 14604223742 0022546 0 ustar 00root root 0000000 0000000 components:
schemas:
ObjectD:
type: object
properties:
result:
$ref: '#/components/schemas/ObjectE'
ObjectE:
properties:
name:
$ref: issue235.spec2.yml#/components/schemas/ObjectX
kin-openapi-0.124.0/openapi3/testdata/issue235.spec2.yml 0000664 0000000 0000000 00000000156 14604223742 0022545 0 ustar 00root root 0000000 0000000 components:
schemas:
ObjectX:
type: object
properties:
name:
type: string
kin-openapi-0.124.0/openapi3/testdata/issue241.yml 0000664 0000000 0000000 00000000370 14604223742 0021525 0 ustar 00root root 0000000 0000000 openapi: 3.0.3
components:
schemas:
FooBar:
type: object
properties:
type_url:
type: string
value:
type: string
format: byte
info:
title: sample
version: version not set
paths: {}
kin-openapi-0.124.0/openapi3/testdata/issue409.yml 0000664 0000000 0000000 00000001052 14604223742 0021531 0 ustar 00root root 0000000 0000000 openapi: 3.0.3
info:
description: Contains Patterns that can't be compiled by the go regexp engine
title: Issue 409
version: 0.0.1
paths:
/v1/apis/{apiID}:
get:
description: Get a list of all Apis and there versions for a given workspace
operationId: getApisV1
parameters:
- description: The ID of the API
in: path
name: apiID
required: true
schema:
type: string
pattern: ^[a-zA-Z0-9]{0,4096}$
responses:
"200":
description: OK
kin-openapi-0.124.0/openapi3/testdata/issue570.json 0000664 0000000 0000000 00000461325 14604223742 0021715 0 ustar 00root root 0000000 0000000 {
"swagger": "2.0",
"info": {
"version": "internal",
"title": "Rubrik INTERNAL REST API",
"description": "Copyright © 2017-2021 Rubrik Inc.\n\n# Introduction\n\nThis is the INTERNAL REST API for Rubrik. We don't guarantee support or backward compatibility. Use at your own risk.\n\n# Changelog\n\n Revisions are listed with the most recent revision first.\n ### Changes to Internal API in Rubrik version 6.0\n ## Breaking changes:\n * Renamed field `node` to `nodeId` for object `NetworkInterface` used by\n `GET /cluster/{id}/network_interface`.\n * Removed `compliance24HourStatus` in `DataSourceTableRequest` for\n `POST /report/data_source/table`.\n Use `complianceStatus`, `awaitingFirstFull`, and `snapshotRange`\n as replacements.\n * Changed the sort_by attribute of `GET /vcd/vapp` to use\n `VcdVappObjectSortAttribute`.\n This attribute no longer uses the `VappCount` or `ConnectionStatus`\n parameters from the previously used `VcdHierarchyObjectSortAttribute`.\n\n ## Feature additions/improvements:\n * Added the `GET /sla_domain/{id}/protected_objects` endpoint to return\n objects explicitly protected by the SLA Domain with direct assignments.\n * Added new field `nodeName` for object `NetworkInterface` used by\n `GET /cluster/{id}/network_interface`.\n * Added the `POST /cluster/{id}/remove_nodes` endpoint to trigger a bulk\n node removal job.\n * Added new optional field `numChannels` to `ExportOracleDbConfig` object\n specifying the number of channels used during Oracle clone or same-host\n recovery.\n * Added new optional fields `forceFull` to the object\n `HypervVirtualMachineSummary` used by `GET /hyperv/vm`. This field is also\n used in `HypervVirtualMachineDetail` used by `GET /hyperv/vm/{id}` and\n `PATCH /hyperv/vm/{id}`.\n * Added the `GET /cluster/{id}/bootstrap_config` endpoint to enable Rubrik CDM\n to retrieve Rubrik cluster configuration information for the cluster nodes.\n * Added new optional field clusterUuid to the ClusterConfig object used\n by `POST /cluster/{id}/bootstrap` and `POST /cluster/{id}/setupnetwork`.\n * Added new optional fields `dataGuardGroupId` and `dataGuardGroupName` to\n the object `OracleHierarchyObjectSummary` used by\n `GET /oracle/hierarchy/{id}`, `GET /oracle/hierarchy/{id}/children`, and\n `GET /oracle/hierarchy/{id}/descendants`.\n * Added new optional fields `dataGuardGroupId` and `dataGuardGroupName` to\n the object `OracleDbSummary` used by `GET /oracle/db`.\n * Added new optional fields `dataGuardGroupId` and `dataGuardGroupName` to\n the object `OracleDbDetail` used by `GET /oracle/db/{id}` and\n `PATCH /oracle/db/{id}`.\n * Added a new optional field `immutabilityLockSummary` to the object\n `ArchivalLocationSummary` returned by GET `/archive/location` and\n GET `/organization/{id}/archive/location`\n * Added new optional fields `dbUniqueName` and `databaseRole` to the object\n `OracleHierarchyObjectSummary` used by `GET /oracle/hierarchy/{id}`,\n `GET /oracle/hierarchy/{id}/children`, and\n `GET /oracle/hierarchy/{id}/descendants`.\n * Added new required fields `dbUniqueName` and `databaseRole` to the object\n `OracleDbSummary` used by `GET /oracle/db`.\n * Added a new required field `databaseRole` to the object `OracleDbDetail`\n used by `GET /oracle/db/{id}` and `PATCH /oracle/db/{id}`.\n * Added a new optional field `subnet` to `ManagedVolumeUpdate`, used by \n `PATCH /managed_volume/{id}` for updating the subnet to which the node IPs\n will belong during an SLA MV backup.\n * Added new optional field `numChannels` to `RecoverOracleDbConfig`\n and `MountOracleDbConfig` objects specifying the number of channels used\n during Oracle recovery.\n * Added a new optional field `immutabilityLockSummary` to the object\n `ObjectStoreLocationSummary` and `ObjectStoreUpdateDefinition` used by\n `GET/POST /archive/object_store` and `GET/POST /archive/object_store/{id}`\n * Added a new optional field `errorMessage` to `SupportTunnelInfo` object \n used by `GET /node/{id}/support_tunnel` and\n `PATCH /node/{id}/support_tunnel`.\n * Added new optional field `cloudStorageLocation` to the `ClusterConfig`\n object used by `POST /cluster/{id}/bootstrap`.\n * Added new enum `Disabled` to `DataLocationOwnershipStatus`\n used by `ArchivalLocationSummary`\n * Added a new optional field `installTarball` to the `ClusterConfig`\n object used by `POST /cluster/{id}/bootstrap`.\n * Added a new optional field `clusterInstall` to the `ClusterConfigStatus`\n object used by `GET /cluster/{id}/bootstrap`.\n * Added the `GET /cluster/{id}/install` endpoint to return the current\n status of Rubrik CDM install on a cluster.\n * Added the `POST /cluster/{id}/install` endpoint to allow Rubrik CDM \n install on cluster nodes which are not bootstrapped.\n * Added the `GET /cluster/{id}/packages` endpoint to return the list of\n Rubrik CDM packages available for installation.\n * Updated `request_id` parameter in the `GET /cluster/{id}/bootstrap` \n endpoint, as not required.\n * Updated `request_id` parameter in the `GET /cluster/{id}/install` \n endpoint, as not required.\n * Updated `BootstrappableNodeInfo` returned by `GET /cluster/{id}/discover`\n endpoint to include the `version` field, to indicate the\n Rubrik CDM software version.\n * Added a new optional field `isSetupNetworkOnly` to the `ClusterConfig`\n object used by `POST /cluster/{id}/setupnetwork`.\n * Added the `POST /cluster/{id}/setupnetwork` endpoint to enable Rubrik CDM\n to perform network setup on nodes that are not bootstrapped.\n * Added the `GET /cluster/{id}/setupnetwork` endpoint to return the current\n status of setup network command on node or nodes.\n * Added a new optional field `hostname` to the `NodeStatus` object used by\n `GET /cluster/{id}/node`, `GET /node`, `GET /node/stats`, `GET /node/{id}`,\n and `GET /node/{id}/stats`.\n * Added new optional fields `usedFastVhdx` and `fileSizeInBytes` to the\n `HypervVirtualMachineSnapshotSummary` returned by the API\n `GET /hyperv/vm/{id}/snapshot`.\n * Added the `GET /archive/location/request/{id}` endpoint to query the status\n of asynchronous archival location requests.\n\n ## Deprecation:\n * Deprecated the following Oracle endpoints\n * `GET /oracle/db`\n * `GET /oracle/db/{id}`\n * `PATCH /oracle/db/{id}`\n * Deprecated the following vcd hierarchy endpoints. \n * `GET /vcd/hierarchy/{id}`\n * `GET /vcd/hierarchy/{id}/children`\n * `GET /vcd/hierarchy/{id}/descendants`\n * Deprecated the following vcd cluster endpoints.\n * `GET /vcd/cluster`\n * `POST /vcd/cluster`\n * `GET /vcd/cluster/{id}/vimserver`\n * `POST /vcd/cluster/{id}/refresh`\n * `GET /vcd/cluster/{id}`\n * `PATCH /vcd/cluster/{id}`\n * `DELETE /vcd/cluster/{id}`\n * `GET /vcd/cluster/request/{id}`\n * Deprecated the following vcd vapp endpoints.\n * `GET /vcd/vapp`\n * `GET /vcd/vapp/{id}`\n * `PATCH /vcd/vapp/{id}`\n * `GET /vcd/vapp/{id}/snapshot`\n * `POST /vcd/vapp/{id}/snapshot`\n * `DELETE /vcd/vapp/{id}/snapshot`\n * `GET/vcd/vapp/snapshot/{id}`\n * `DELETE /vcd/vapp/snapshot/{id}`\n * `GET /vcd/vapp`\n * `GET /vcd/vapp/{id}/missed_snapshot`\n * `GET /vcd/vapp/snapshot/{snapshot_id}/export/options`\n * `POST /vcd/vapp/snapshot/{snapshot_id}/export`\n * `POST /vcd/vapp/snapshot/{snapshot_id}/instant_recover`\n * `GET /vcd/vapp/snapshot/{snapshot_id}/instant_recover/options`\n * `GET /vcd/vapp/request/{id}`\n * `GET /vcd/vapp/{id}/search`\n * `POST /vcd/vapp/snapshot/{id}/download`\n\n ### Changes to Internal API in Rubrik version 5.3.2\n ## Deprecation:\n * Deprecated `compliance24HourStatus` in `DataSourceTableRequest` for\n `POST /report/data_source/table`.\n Use `complianceStatus`, `awaitingFirstFull`, and `snapshotRange`\n as replacements.\n\n ### Changes to Internal API in Rubrik version 5.3.1\n ## Breaking changes:\n * Added new required field `isPwdEncryptionSupported` to\n the API response `PlatformInfo` for password-based encryption at rest\n in the API `GET /cluster/{id}/platforminfo`.\n\n ## Feature additions/improvements:\n * Added new field `hostsInfo` to OracleHierarchyObjectSummary\n returned by `GET /oracle/hierarchy/{id}/children`.\n * Added new field `hostsInfo` to OracleHierarchyObjectSummary\n returned by `GET /oracle/hierarchy/{id}/descendants`.\n * Added new field `hostsInfo` to OracleHierarchyObjectSummary\n returned by `GET /oracle/hierarchy/{id}`.\n * Added `shouldKeepConvertedDisksOnFailure` as an optional field in\n CreateCloudInstanceRequest definition used in the on-demand API\n conversion API `/cloud_on/aws/instance` and `/cloud_on/azure/instance`.\n This will enable converted disks to be kept on failure for CloudOn\n conversion.\n * Added the `hostsInfo` field to the OracleDbDetail that the\n `GET /oracle/db/{id}` and `PATCH /oracle/db/{id}` endpoints return.\n * Added new optional field `isOnNetAppSnapMirrorDestVolume` to\n HostShareParameters to support backup of NetApp SnapMirror\n destination volume.\n * Added new optional fields `encryptionPassword` and\n `newEncryptionPassword` to the KeyRotationOptions to support\n key rotation for password-based encryption at rest in\n internal API `POST /cluster/{id}/security/key_rotation`.\n * Added `Index` to `ReportableTaskType`.\n * Added new optional field `totpStatus` in `UserDetail` for\n showing the TOTP status of the user with the endpoint\n `GET /internal/user/{id}`\n * Added new optional field `isTotpEnforced` in `UserDefinition` for\n configuring the TOTP enforcement for the user with the endpoint\n `POST /internal/user`\n * Added new optional field `isTotpEnforced` in `UserUpdateInfo` for\n configuring the TOTP enforcement for the user with the endpoint\n `PATCH /internal/user/{id}`\n * Added a new field `HypervVirtualDiskInfo` to HypervVirtualMachineDetail \n used by `GET /hyperv/vm/{id}`.\n * Added a new field `virtualDiskIdsExcludedFromSnapshot` to \n HypervVirtualMachineUpdate used by `PATCH /hyperv/vm/{id}`.\n\n ### Changes to Internal API in Rubrik version 5.3.0\n ## Deprecation:\n * Deprecated `GET /authorization/role/admin`,\n `GET /authorization/role/compliance_officer`,\n `GET /authorization/role/end_user`,\n `GET /authorization/role/infra_admin`,\n `GET /authorization/role/managed_volume_admin`,\n `GET /authorization/role/managed_volume_user`,\n `GET /authorization/role/org_admin`,\n `GET /authorization/role/organization`,\n `GET /authorization/role/read_only_admin` endpoints. Use the new\n v1 endpoints for role management.\n * Deprecated `SnapshotCloudStorageTier` enum value Cold. It will be left,\n but will be mapped internally to the new value, AzureArchive, which is\n recommended as a replacement.\n * Deprecated the `GET /snapshot/{id}/storage/stats` endpoint. Use the v1\n version when possible.\n * Deprecated `POST /hierarchy/bulk_sla_conflicts`. It is migrated to\n v1 and using that is recommended.\n * Deprecated `GET /mssql/availability_group`,\n `GET /mssql/availability_group/{id}`,\n `PATCH /mssql/availability_group/{id}`, `PATCH /mssql/db/bulk`,\n `POST /mssql/db/bulk/snapshot`, `GET /mssql/db/bulk/snapshot/{id}`,\n `GET /mssql/db/count`, `DELETE /mssql/db/{id}/recoverable_range/download`,\n `GET /mssql/db/{id}/compatible_instance`, `GET /mssql/instance/count`,\n `GET /mssql/db/{id}/restore_estimate`, `GET /mssql/db/{id}/restore_files`,\n `GET /mssql/db/{id}/snappable_id`, `GET /mssql/db/defaults`,\n `PATCH /mssql/db/defaults` and `GET /mssql/db/recoverable_range/download/{id}`\n endpoints. Use the v1 version when possible.\n ## Breaking changes:\n * Added new Boolean field `isLinkLocalIpv4Mode` to `AddNodesConfig` and\n `ReplaceNodeConfig`.\n * Changed the type for ReplicationSnapshotLag, which is used by /report/{id} GET\n and PATCH endpoints from integer to string.\n * Added new required field `objectStore` to DataSourceDownloadConfig used by\n `POST /report/data_source/download`.\n * Removed the `storageClass` field from the DataSourceDownloadConfig object used\n by the `POST /report/data_source/download` endpoint. The value was not used.\n * Removed endpoint `GET /mfa/rsa/server` and moved it to v1.\n * Removed endpoint `POST /mfa/rsa/server` and moved it to v1.\n * Removed endpoint `GET /mfa/rsa/server/{id}` and moved it to v1.\n * Removed endpoint `PATCH /mfa/rsa/server/{id}` and moved it to v1.\n * Removed endpoint `DELETE /mfa/rsa/server/{id}` and moved it to v1.\n * Removed endpoint `PUT /cluster/{id}/security/web_signed_cert`\n and moved it to v1.\n * Removed endpoint `DELETE /cluster/{id}/security/web_signed_cert`\n and moved it to v1\n * Removed endpoint `PUT /cluster/{id}/security/kmip/client` and added it\n to v1.\n * Removed endpoint `GET /cluster/{id}/security/kmip/client` and added it\n to v1.\n * Removed endpoint `GET /cluster/{id}/security/kmip/server` and added it\n to v1.\n * Removed endpoint `PUT /cluster/{id}/security/kmip/server` and added it\n to v1.\n * Removed endpoint `DELETE /cluster/{id}/security/kmip/server` and added\n it to v1.\n * Removed endpoint `POST /replication/global_pause`. To toggle replication\n pause between enabled and disabled, use\n `POST /v1/replication/location_pause/disable` and\n `POST /v1/replication/location_pause/enable` instead.\n * Removed `GET /replication/global_pause`. To retrieve replication pause\n status, use `GET /internal/replication/source` and\n `GET /internal/replication/source/{id}` instead.\n * Removed `GET /node_management/{id}/fetch_package` since it was never used.\n * Removed `GET /node_management/{id}/upgrade` since it was never used.\n * Removed `POST /node_management/{id}/fetch_package` since it was never used.\n * Removed `POST /node_management/{id}/upgrade` since it was never used.\n\n ## Feature additions/improvements:\n * Added new optional field `pubKey` to the GlobalManagerConnectionUpdate\n object and the GlobalManagerConnectionInfo object used by\n `GET /cluster/{id}/global_manager` and `PUT /cluster/{id}/global_manager`.\n * Added a new optional field `storageClass` to the `ArchivalLocationSummary`\n type.\n * Added optional field `StartMethod` to the following components: \n ChartSummary, TableSummary, ReportTableRequest, FilterSummary and\n RequestFilters.\n * Added new enum field `StackedReplicationComplianceCountByStatus` to the\n measure property in ChartSummary.\n * Added new enum fields `ReplicationInComplianceCount`,\n `ReplicationNonComplianceCount` to the following properties:\n measure property in ChartSummary, column property in TableSummary,\n and sortBy property in ReportTableRequest.\n * Added the endpoint `GET /vmware/config/datastore_freespace_threshold` to\n query the VMware datastore freespace threshold config.\n * Added the endpoint `PATCH /vmware/config/set_datastore_freespace_threshold`\n to update the VMware datastore freespace threshold config.\n * Added two new optional query parameters `offset` and `limit` to\n `GET /organization`.\n * Added two new optional query parameters `offset` and `limit` to\n `GET /user/{id}/organization`.\n * Modified `SnapshotCloudStorageTier`, enum adding values AzureArchive, Glacier,\n and GlacierDeepArchive.\n * Added the `lastValidationResult` field to the OracleDbDetail that the\n `GET /oracle/db/{id}` and `PATCH /oracle/db/{id}` endpoints return.\n * Added `isValid` field to the OracleDbSnapshotSummary of\n OracleRecoverableRange that the `GET /oracle/db/\n {id}/recoverable_range` endpoint returns.\n * Added the `isRemoteGlobalBlackoutActive` field to the\n ReplicationSourceSummary object that the\n `GET /organization/{id}/replication/source` endpoint returns.\n * Added the `isRemoteGlobalBlackoutActive` field to the\n ReplicationSourceSummary object that the\n `GET /replication/source/{id}` endpoint returns.\n * Added the `isRemoteGlobalBlackoutActive` field to the\n ReplicationSourceSummary object that the\n `GET /replication/source` endpoint returns.\n * Added the `isReplicationTargetPauseEnabled` field to the\n ReplicationSourceSummary object that the\n `GET /organization/{id}/replication/source` endpoint returns.\n * Added the `isReplicationTargetPauseEnabled` field to the\n ReplicationSourceSummary object that the\n `GET /replication/source/{id}` endpoint returns.\n * Added the `isReplicationTargetPauseEnabled` field to the\n ReplicationSourceSummary object that the\n `GET /replication/source` endpoint returns.\n * Added new optional field `cloudRehydrationSpeed` to the\n ObjectStoreLocationSummary, ObjectStoreUpdateDefinition,\n PolarisAwsArchivalLocationSpec, and PolarisAzureArchivalLocationSpec\n objects to specify the rehydration speed to use when performing cloud\n rehydration on objects tiered cold storage.\n * Added new optional field earliestTimestamp to the `POST\n /polaris/export_info` endpoint to enable incremental MDS synchronization.\n * Added new values `RetentionSlaDomainName` , `ObjectType`, `SnapshotCount`,\n `AutoSnapshotCount` and `ManualSnapshotCount` to\n `UnmanagedObjectSortAttribute` field of the `GET /unmanaged_object` endpont.\n * Added new optional field `endpoint` to the ObjectStorageDetail\n object used by several Polaris APIs.\n * Added new optional field `accessKey` to the ObjectStorageConfig\n object used by several Polaris APIs.\n * Added new optional field `endpoint` to DataSourceDownloadConfig used by\n `POST /report/data_source/download`.\n * Added new field `slaClientConfig` to the `ManagedVolumeUpdate`\n object used by the `PATCH /managed_volume/{id}` endpoint to enable\n edits to the configuration of SLA Managed Volumes.\n * Added new field `shouldSkipPrechecks` to DecommissionNodesConfig used by\n `POST /cluster/{id}/decommission_nodes`.\n * Added new query parameter `managed_volume_type` to allow filtering\n managed volumes based on their type using the `GET /managed_volume`\n endpoint.\n * Added new query parameter `managed_volume_type` to allow filtering\n managed volume exports based on their source managed volume type\n using the `GET /managed_volume/snapshot/export` endpoint.\n * Added the new fields `mvType` and `slaClientConfig` to the\n `ManagedVolumeConfig` object. These fields are used with the\n `POST /managed_volume` endpoint to manage SLA Managed Volumes.\n * Added the new fields `mvType` and `slaManagedVolumeDetails` to the\n `ManagedVolumeSummary` object returned by the `GET /managed_volume`,\n `POST /managed_volume`, `GET /managed_volume/{id}` and\n `POST /managed_volume/{id}` endpoints.\n * Added new field `mvType` to the `ManagedVolumeSnapshotExportSummary`\n object returned by the `GET /managed_volume/snapshot/export` and\n `GET /managed_volume/snapshot/export/{id}` endpoints.\n * Added optional field `hostMountPoint` in the `ManagedVolumeChannelConfig`.\n `ManagedVolumeChannelConfig` is returned as part of\n `ManagedVolumeSnapshotExportSummary`, which is returned\n by the `GET /managed_volume/snapshot/export` and\n `GET /managed_volume/snapshot/export/{id}` endpoints.\n * Added `POST /managed_volume/{id}/snapshot` method to take an on\n demand snapshot for SLA Managed Volumes.\n * Added new field `isPrimary` to OracleDbSummary returned by\n `GET /oracle/db`.\n * Added new field `isPrimary` to OracleDbDetail returned by\n `GET /oracle/db/{id}` and `PATCH /oracle/db/{id}`.\n * Added new field `isOracleHost` to HostDetail\n returned by `GET /host/{id}`.\n * Added optional isShareAutoDiscoveryAndAdditionEnabled in the\n NasBaseConfig and NasConfig.\n NasBaseConfig is returned as part of HostSummary, which is returned by the\n `Get /host/envoy` and `Get /host` endpoints. NasConfig is used by\n HostRegister and HostUpdate. The HostRegister field is used by the\n `Post /host/bulk` endpoint and the HostUpdate is field used by the\n `PATCH /host/bulk` endpoint.\n * Added new endpoint `POST /managed_volume/{id}/resize` to resize managed\n volume to a larger size.\n * Added ReplicationComplianceStatus as an optional field to the TableSummary\n which is used by /report/{id} GET and PATCH endpoints and to RequestFilters\n which is used by /report/data_source/table.\n * Added `PATCH /cluster/{id}/trial_edge` endpoint to extend the trial period.\n * Added new optional fields `extensionsLeft` and `daysLeft` to\n EdgeTrialStatus returned by `GET /cluster/{id}/trial_edge` and\n `PATCH /cluster/{id}/trial_edge`.\n * Added new endpoint `POST /managed_volume/snapshot/{id}/restore` to export a\n managed volume snapshot and mount it on a host.\n * Added new endpoints `PATCH /config/{component}/reset` to allow configs to\n be reset to DEFAULT state.\n * Added a new field `logRetentionTimeInHours` to the `MssqlDbDefaults`\n object returned by the `GET /mssql/db/defaults` and\n `PATCH /mssql/db/defaults` endpoints.\n * Added new optional field `logRetentionTimeInHours` to `MssqlDbDefaultsUpdate`\n object which is used by `PATCH /mssql/db/defaults`.\n * Added new optional field `unreadable` to `BrowseResponse` and\n `SnapshotSearchResponse`, which are used by `GET /browse` and\n `GET /search/snapshot_search` respectively.\n * Added MissedReplicationSnapshots as an optional field to the TableSummary\n which is used by /report/{id} GET and PATCH endpoints.\n * Added new optional field `pitRecoveryInfo` to `ChildSnappableFailoverInfo`\n object which is used by `PUT /polaris/failover/target/{id}/start`\n * Added ReplicationDataLag as an optional field to the TableSummary\n which is used by /report/{id} GET and PATCH endpoints.\n * Added UnreplicatedSnapshots as an optional field to the TableSummary\n which is used by /report/{id} GET and PATCH endpoints.\n * Added the field `networkAdapterType` to `VappVmNetworkConnection`.\n `VappVmNetworkConnection` is returned by the\n `GET /vcd/vapp/snapshot/{snapshot_id}/instant_recover/options` and\n `GET /vcd/vapp/snapshot/{snapshot_id}/export/options` endpoints and is\n used by the `POST /vcd/vapp/snapshot/{snapshot_id}/export` and\n `POST /vcd/vapp/snapshot/{snapshot_id}/instant_recover` endpoints.\n Also added `VcdVmSnapshotDetail`, which is returned by the\n `GET /vcd/vapp/snapshot/{id}` endpoint.\n * Added new endpoint `GET /report/template` to return details\n of a report template.\n * Added new endpoint `POST /report/{id}/send_email` to send an email of the report.\n ## Breaking changes:\n * Made field `restoreScriptSmbPath` optional in `VolumeGroupMountSummary`.\n Endpoints `/volume_group/snapshot/mount` and\n `/volume_group/snapshot/mount/{id}` are affected by this change.\n * Moved endpoints `GET /volume_group`, `GET /volume_group/{id}`,\n `PATCH /volume_group/{id}`, `GET /volume_group/{id}/snapshot`,\n `POST /volume_group/{id}/snapshot`, `GET /volume_group/snapshot/{id}`,\n `GET /volume_group/snapshot/mount`, and\n `GET /volume_group/snapshot/mount/{id}` from internal to v1.\n * Moved endpoint `GET /host/{id}/volume` from internal to v1.\n\n ### Changes to Internal API in Rubrik version 5.2.2\n ## Feature Additions/improvements:\n * Added new field `exposeAllLogs` to ExportOracleTablespaceConfig\n used by `POST /oracle/db/{id}/export/tablespace`.\n\n ### Changes to Internal API in Rubrik version 5.2.1\n ## Feature Additions/improvements:\n * Added new field `shouldBlockOnNegativeFailureTolerance` to\n DecommissionNodesConfig used by `POST /cluster/{id}/decommission_nodes`.\n\n ### Changes to Internal API in Rubrik version 5.2.0\n ## Deprecation:\n * Deprecating `GET /replication/global_pause`. Use\n `GET /internal/replication/source` and\n `GET /internal/replication/source/{id}` to retrieve replication\n pause status in CDM v5.3.\n * Deprecating `POST /replication/global_pause`. Use\n `POST /v1/replication/location_pause/disable` and\n `POST /v1/replication/location_pause/enable` to toggle replication\n pause in CDM v5.3.\n * Deprecating `slaId` field returned by `GET /vcd/vapp/{id}/snapshot`.\n See **snapshotRetentionInfo** to track retention for\n snapshots.\n * Deprecating `slaId` field returned by `GET /vcd/vapp/snapshot/{id}`.\n See **snapshotRetentionInfo** to track retention for\n snapshots.\n * Deprecating `slaId` field returned by `GET /oracle/db/{id}/snapshot`.\n See **snapshotRetentionInfo** to track retention for\n snapshots.\n * Deprecating `slaId` field returned by `GET /oracle/db/\n {id}/recoverable_range`.\n See **snapshotRetentionInfo** to track retention for\n snapshots.\n * Deprecating `slaId` field returned by `GET /oracle/db/snapshot/{id}`.\n See **snapshotRetentionInfo** to track retention for\n snapshots.\n * Deprecating `slaId` field returned by `GET /hyperv/vm/{id}/snapshot`.\n See **snapshotRetentionInfo** to track retention for\n snapshots.\n * Deprecating `slaId` field returned by `GET /hyperv/vm/snapshot/{id}`.\n See **snapshotRetentionInfo** to track retention for\n snapshots.\n * Deprecating `slaId` field returned by `GET /volume_group/{id}/snapshot`.\n See **snapshotRetentionInfo** to track retention for\n snapshots.\n * Deprecating `slaId` field returned by `GET /volume_group/snapshot/{id}`.\n See **snapshotRetentionInfo** to track retention for\n snapshots.\n * Deprecating `slaId` field returned by `GET /storage/array_volume_group\n/{id}/snapshot`.\n See **snapshotRetentionInfo** to track retention for\n snapshots.\n* Deprecating `slaId` field returned by `GET /vcd/vapp/{id}`.\n See **snapshotRetentionInfo** to track retention for\n snapshots.\n* Deprecating `slaId` field returned by `GET /host_fileset/{id}`.\n See **snapshotRetentionInfo** to track retention for\n snapshots.\n* Deprecating `slaId` field returned by `GET /host_fileset/share/{id}`.\n See **snapshotRetentionInfo** to track retention for\n snapshots.\n* Deprecating `slaId` field returned by `GET /app_blueprint/{id}/snapshot`.\n See **snapshotRetentionInfo** to track retention for\n snapshots.\n* Deprecating `slaId` field returned by `GET /app_blueprint/snapshot/{id}`.\n See **snapshotRetentionInfo** to track retention for\n snapshots.\n* Deprecating `slaId` field returned by `GET /managed_volume/{id}/snapshot`.\n See **snapshotRetentionInfo** to track retention for\n snapshots.\n* Deprecating `slaId` field returned by `POST /managed_volume/{id\n}/end_snapshot`.\n See **snapshotRetentionInfo** to track retention for\n snapshots.\n* Deprecating `slaId` field returned by `GET /managed_volume/snapshot/{id}`.\n See **snapshotRetentionInfo** to track retention for\n snapshots.\n* Deprecating `slaId` field returned by `GET /aws/ec2_instance/{id}/snapshot`.\n See **snapshotRetentionInfo** to track retention for\n snapshots.\n* Deprecating `slaId` field returned by `GET /aws/ec2_instance/snapshot/{id}`.\n See **snapshotRetentionInfo** to track retention for\n snapshots.\n* Deprecating `slaId` field returned by `GET /nutanix/vm/{id}/snapshot`.\n See **snapshotRetentionInfo** to track retention for\n snapshots.\n* Deprecating `slaId` field returned by `GET /nutanix/vm/snapshot/{id}`.\n See **snapshotRetentionInfo** to track retention for\n snapshots.\n* Deprecating `slaId` field returned by `GET /fileset/bulk`.\n See **snapshotRetentionInfo** to track retention for\n snapshots.\n* Added a new field `pendingSlaDomain` to `VirtualMachineDetail`\n object referred by `VappVmDetail` returned by\n `GET /vcd/vapp/{id}` and `PATCH /vcd/vapp/{id}`\n * Deprecated `POST /internal/vmware/vcenter/{id}/refresh_vm` endpoint. Use\n `POST /v1/vmware/vcenter/{id}/refresh_vm` instead to refresh a\n virtual machine by MOID.\n\n ## Breaking changes:\n* Rename the field configuredSlaDomainId in the OracleUpdate object to\n configuredSlaDomainIdDeprecated and modify the behavior so\n configuredSlaDomainIdDeprecated is only used to determine log backup\n frequency and not to set retention time.\n* Removed `GET /event/count_by_status` endpoint and it will be\n replaced by `GET /job_monitoring/summary_by_job_state`.\n* Removed `GET /event/count_by_job_type` endpoint and it will be\n replaced by `GET /job_monitoring/summary_by_job_type`.\n* Removed `GET /event_series` endpoint and it will be replaced by\n `GET /job_monitoring`.\n* Refactor `PUT /cluster/{id}/security/web_signed_cert` to accept\n certificate_id instead of X.509 certificate text. Also removed\n the `POST /cluster/{id}/security/web_csr` endpoint.\n * Refactor `GET /rsa-server`, `POST /rsa-server`, `GET /rsa-server/{id}`,\n and `PATCH /rsa-server/{id}` to take in a certificate ID instead of\n a certificate.\n * Changed definition of CloudInstanceUpdate by updating the enums ON/OFF\n to POWERSTATUS_ON/POWERSTATUS_OFF\n * Removed `GET /event_series/{status}/csv_link` endpoint to download CSV\n with job monitoring information. It has been replaced by the\n `GET /job_monitoring//csv_download_link` v1 endpoint.\n * Removed GET `/report/summary/physical_storage_time_series`. Use\n GET `/stats/total_physical_storage/time_series` instead.\n * Removed GET `/report/summary/average_local_growth_per_day`. Use\n GET `/stats/average_storage_growth_per_day` instead.\n * Removed POST `/job/instances/`. Use GET `/job/{job_id}/instances` instead.\n * Removed the POST `/cluster/{id}/reset` endpoint.\n * Removed GET `/user`. Use the internal POST `/principal_search`\n or the v1 GET `/principal` instead for querying any principals,\n including users.\n\n ## Feature additions/improvements:\n * Added the `GET /replication/global_pause` endpoint to return the current\n status of global replication pause. Added the `POST /replication/global_pause`.\n endpoint to toggle the replication target global pause jobs status. When\n global replication pause is enabled, all replication jobs on the local\n cluster are paused. When disabling global replication pause, optional\n parameter `shouldOnlyReplicateNewSnapshots` can be set to `true` to only\n replicate snapshots taken after disabling the pause. These endpoints must\n be used at the target cluster.\n * Added new field `parentSnapshotId` to AppBlueprintSnapshotSummary returned\n by `GET /app_blueprint/{id}/snapshot`.\n * Added new field `parentSnapshotId` to AppBlueprintSnapshotDetail returned\n by `GET /app_blueprint/snapshot/{id}`.\n * Added new field `parentSnapshotId` to AwsEc2InstanceSummary returned by\n `GET /aws/ec2_instance`.\n * Added new field `parentSnapshotId` to AwsEc2InstanceDetail returned by\n `GET /aws/ec2_instance/{id}`.\n * Added new field `parentSnapshotId` to AwsEc2InstanceDetail returned by\n `PATCH /aws/ec2_instance/{id}`.\n * Added new field `parentSnapshotId` to HypervVirtualMachineSnapshotSummary\n returned by `GET /hyperv/vm/{id}/snapshot`.\n * Added new field `parentSnapshotId` to HypervVirtualMachineSnapshotDetail\n returned by `GET /hyperv/vm/snapshot/{id}`.\n * Added new field `parentSnapshotId` to ManagedVolumeSnapshotSummary\n returned by `GET /managed_volume/{id}/snapshot`.\n * Added new field `parentSnapshotId` to ManagedVolumeSnapshotSummary\n returned by `POST /managed_volume/{id}/end_snapshot`.\n * Added new field `parentSnapshotId` to ManagedVolumeSnapshotDetail returned\n by `GET /managed_volume/snapshot/{id}`.\n * Added new field `parentSnapshotId` to NutanixVmSnapshotSummary returned by\n `GET /nutanix/vm/{id}/snapshot`.\n * Added new field `parentSnapshotId` to NutanixVmSnapshotDetail returned by\n `GET /nutanix/vm/snapshot/{id}`.\n * Added new field `parentSnapshotId` to OracleDbSnapshotSummary returned by\n `GET /oracle/db/{id}/snapshot`.\n * Added new field `parentSnapshotId` to OracleDbSnapshotDetail returned by\n `GET /oracle/db/snapshot/{id}`.\n * Added new field `parentSnapshotId` to StorageArrayVolumeGroupSnapshotSummary\n returned by `GET /storage/array_volume_group/{id}/snapshot`.\n * Added new field `parentSnapshotId` to StorageArrayVolumeGroupSnapshotDetail\n returned by `GET /storage/array_volume_group/snapshot/{id}`.\n * Added new field `parentSnapshotId` to VcdVappSnapshotSummary returned by\n `GET /vcd/vapp/{id}/snapshot`.\n * Added new field `parentSnapshotId` to VcdVappSnapshotDetail returned by\n `GET /vcd/vapp/snapshot/{id}`.\n * Added new field `parentSnapshotId` to VolumeGroupSnapshotSummary returned by\n `GET /volume_group/{id}/snapshot`.\n * Added new field `parentSnapshotId` to VolumeGroupSnapshotDetail returned by\n `GET /volume_group/snapshot/{id}`.\n * Added new field `retentionSlaDomanId` to MssqlAvailabilityGroupSummary\n returned by `GET /mssql/availability_group`.\n * Added new field `retentionSlaDomanId` to MssqlAvailabilityGroupDetail\n returned by `GET /mssql/availability_group/{id}`.\n * Added new field `retentionSlaDomanId` to MssqlAvailabilityGroupDetail\n returned by `PATCH /mssql/availability_group/{id}`.\n * Added new field `retentionSlaDomainId` to UnmanagedObjectSummary\n returned by `GET /unmanaged_object`.\n * Added new field `retentionSlaDomainId` to ManagedVolumeSummary\n returned by `GET /managed_volume`.\n * Added new field `retentionSlaDomainId` to AppBlueprintDetail\n returned by `GET /app_blueprint/{id}`.\n * Added new field `retentionSlaDomainId` to AppBlueprintDetail\n returned by `PATCH /polaris/app_blueprint/{id}`.\n * Added new field `retentionSlaDomainId` to AppBlueprintDetail\n returned by `POST /polaris/app_blueprint`.\n * Added new field `retentionSlaDomainId` to AppBlueprintExportSnapshotJobConfig\n returned by `POST /polaris/app_blueprint/snapshot/{id}/export`.\n * Added new field `retentionSlaDomainId` to AppBlueprintInstantRecoveryJobConfig\n returned by `POST /polaris/app_blueprint/snapshot/{id}/instant_recover`.\n * Added new field `retentionSlaDomainId` to AppBlueprintMountSnapshotJobConfig\n returned by `POST /polaris/app_blueprint/snapshot/{id}/mount`.\n * Added new field `retentionSlaDomainId` to AppBlueprintSummary\n returned by `GET /app_blueprint`.\n * Added new field `retentionSlaDomainId` to AwsEc2InstanceDetail\n returned by `GET /aws/ec2_instance/{id}`.\n * Added new field `retentionSlaDomainId` to AwsEc2InstanceDetail\n returned by `PATCH /aws/ec2_instance/{id}`.\n * Added new field `retentionSlaDomainId` to AwsEc2InstanceSummary\n returned by `GET /aws/ec2_instance`.\n * Added new field `retentionSlaDomainId` to AwsHierarchyObjectSummary\n returned by `GET /aws/hierarchy/{id}/children`.\n * Added new field `retentionSlaDomainId` to AwsHierarchyObjectSummary\n returned by `GET /aws/hierarchy/{id}/descendants`.\n * Added new field `retentionSlaDomainId` to AwsHierarchyObjectSummary\n returned by `GET /aws/hierarchy/{id}`.\n * Added new field `retentionSlaDomainId` to HypervHierarchyObjectSummary\n returned by `GET /hyperv/hierarchy/{id}/children`.\n * Added new field `retentionSlaDomainId` to HypervHierarchyObjectSummary\n returned by `GET /hyperv/hierarchy/{id}/descendants`.\n * Added new field `retentionSlaDomainId` to HypervHierarchyObjectSummary\n returned by `GET /hyperv/hierarchy/{id}`.\n * Added new field `retentionSlaDomainId` to HypervHierarchyObjectSummary\n returned by `GET /organization/{id}/hyperv`.\n * Added new field `retentionSlaDomainId` to HypervVirtualMachineDetail\n returned by `GET /hyperv/vm/{id}`.\n * Added new field `retentionSlaDomainId` to HypervVirtualMachineDetail\n returned by `PATCH /hyperv/vm/{id}`.\n * Added new field `retentionSlaDomainId` to HypervVirtualMachineSummary\n returned by `GET /hyperv/vm`.\n * Added new field `retentionSlaDomainId` to ManagedHierarchyObjectSummary\n returned by `GET /hierarchy/{id}`.\n * Added new field `retentionSlaDomainId` to ManagedHierarchyObjectSummary\n returned by `GET /hierarchy/{id}/sla_conflicts`.\n * Added new field `retentionSlaDomainId` to ManagedVolumeSummary\n returned by `GET /managed_volume/{id}`.\n * Added new field `retentionSlaDomainId` to ManagedVolumeSummary\n returned by `GET /organization/{id}/managed_volume`.\n * Added new field `retentionSlaDomainId` to ManagedVolumeSummary\n returned by `PATCH /managed_volume/{id}`.\n * Added new field `retentionSlaDomainId` to ManagedVolumeSummary\n returned by `POST /managed_volume`.\n * Added new field `retentionSlaDomainId` to MountDetail\n returned by `GET /vmware/vm/snapshot/mount/{id}`.\n * Added new field `retentionSlaDomainId` to NutanixHierarchyObjectSummary\n returned by `GET /nutanix/hierarchy/{id}/children`.\n * Added new field `retentionSlaDomainId` to NutanixHierarchyObjectSummary\n returned by `GET /nutanix/hierarchy/{id}/descendants`.\n * Added new field `retentionSlaDomainId` to NutanixHierarchyObjectSummary\n returned by `GET /nutanix/hierarchy/{id}`.\n * Added new field `retentionSlaDomainId` to NutanixHierarchyObjectSummary\n returned by `GET /organization/{id}/nutanix`.\n * Added new field `retentionSlaDomainId` to OracleDbDetail\n returned by `GET /oracle/db/{id}`.\n * Added new field `retentionSlaDomainId` to OracleDbDetail\n returned by `PATCH /oracle/db/{id}`.\n * Added new field `retentionSlaDomainId` to OracleDbSummary\n returned by `GET /oracle/db`.\n * Added new field `retentionSlaDomainId` to OracleHierarchyObjectSummary\n returned by `GET /oracle/hierarchy/{id}/children`.\n * Added new field `retentionSlaDomainId` to OracleHierarchyObjectSummary\n returned by `GET /oracle/hierarchy/{id}/descendants`.\n * Added new field `retentionSlaDomainId` to OracleHierarchyObjectSummary\n returned by `GET /oracle/hierarchy/{id}`.\n * Added new field `retentionSlaDomainId` to OracleHierarchyObjectSummary\n returned by `GET /organization/{id}/oracle`.\n * Added new field `retentionSlaDomainId` to SlaConflictsSummary\n returned by `POST /hierarchy/bulk_sla_conflicts`.\n * Added new field `retentionSlaDomainId` to SnappableRecoverySpecDetails\n returned by `POST /polaris/failover/recovery_spec/upsert`.\n * Added new field `retentionSlaDomainId` to SnappableRecoverySpec\n returned by `POST /polaris/failover/recovery_spec/upsert`.\n * Added new field `retentionSlaDomainId` to Snappable\n returned by `POST /polaris/failover/recovery_spec/upsert`.\n * Added new field `retentionSlaDomainId` to Snappable\n returned by `POST /stats/snappable_storage`.\n * Added new field `retentionSlaDomainId` to StorageArrayHierarchyObjectSummary\n returned by `GET /organization/{id}/storage/array`.\n * Added new field `retentionSlaDomainId` to StorageArrayHierarchyObjectSummary\n returned by `GET /storage/array/hierarchy/{id}/children`.\n * Added new field `retentionSlaDomainId` to StorageArrayHierarchyObjectSummary\n returned by `GET /storage/array/hierarchy/{id}/descendants`.\n * Added new field `retentionSlaDomainId` to StorageArrayHierarchyObjectSummary\n returned by `GET /storage/array/hierarchy/{id}`.\n * Added new field `retentionSlaDomainId` to StorageArrayVolumeGroupDetail\n returned by `GET /storage/array_volume_group/{id}`.\n * Added new field `retentionSlaDomainId` to StorageArrayVolumeGroupDetail\n returned by `PATCH /storage/array_volume_group/{id}`.\n * Added new field `retentionSlaDomainId` to StorageArrayVolumeGroupDetail\n returned by `POST /storage/array_volume_group`.\n * Added new field `retentionSlaDomainId` to StorageArrayVolumeGroupSummary\n returned by `GET /storage/array_volume_group`.\n * Added new field `retentionSlaDomainId` to TriggerFailoverOnTargetDefinition\n returned by `PUT /polaris/failover/target/{id}/resume`.\n * Added new field `retentionSlaDomainId` to TriggerFailoverOnTargetDefinition\n returned by `PUT /polaris/failover/target/{id}/start`.\n * Added new field `retentionSlaDomainId` to UpsertSnappableRecoverySpecResponse\n returned by `POST /polaris/failover/recovery_spec/upsert`.\n * Added new field `retentionSlaDomainId` to VcdHierarchyObjectSummary\n returned by `GET /organization/{id}/vcd`.\n * Added new field `retentionSlaDomainId` to VcdHierarchyObjectSummary\n returned by `GET /vcd/hierarchy/{id}/children`.\n * Added new field `retentionSlaDomainId` to VcdHierarchyObjectSummary\n returned by `GET /vcd/hierarchy/{id}/descendants`.\n * Added new field `retentionSlaDomainId` to VcdHierarchyObjectSummary\n returned by `GET /vcd/hierarchy/{id}`.\n * Added new field `retentionSlaDomainId` to VcdVappDetail\n returned by `GET /vcd/vapp/{id}`.\n * Added new field `retentionSlaDomainId` to VcdVappDetail\n returned by `PATCH /vcd/vapp/{id}`.\n * Added new field `retentionSlaDomainId` to VcdVappSnapshotDetail\n returned by `GET /vcd/vapp/snapshot/{id}`.\n * Added new field `retentionSlaDomainId` to VolumeGroupDetail\n returned by `GET /volume_group/{id}`.\n * Added new field `retentionSlaDomainId` to VolumeGroupDetail\n returned by `PATCH /volume_group/{id}`.\n * Added new field `retentionSlaDomainId` to VolumeGroupSummary\n returned by `GET /volume_group`.\n * Added new field `retentionSlaDomainId` to AwsHierarchyObjectSummary\n returned by `GET /organization/{id}/aws`.\n * Added new field `retentionSlaDomainId` to VmwareVmMountSummary\n returned by `GET /vmware/vm/snapshot/mount`.\n * Added new field `retentionSlaDomainId` to VcdVappSummary\n returned by `GET /vcd/vapp`.\n * Added `isReplicationTargetPauseEnabled` to ReplicationTargetSummary\n returned by `GET /replication/target`.\n * Added `isReplicationTargetPauseEnabled` to ReplicationTargetSummary\n returned by `POST /replication/target`.\n * Added `isReplicationTargetPauseEnabled` to ReplicationTargetSummary\n returned by `GET /replication/target/{id}`.\n * Added `isReplicationTargetPauseEnabled` to ReplicationTargetSummary\n returned by `GET /replication/target/{id}`.\n * Added `isReplicationTargetPauseEnabled` to ReplicationTargetSummary\n returned by `PATCH /replication/target/{id}`.\n * Added `isReplicationTargetPauseEnabled` to ReplicationTargetSummary\n returned by `GET /organization/{id}/replication/target`.\n * Added new field `hasSnapshotsWithPolicy` to UnmanagedObjectSummary returned\n by GET `/unmanaged_object`\n * Added new field `slaLastUpdateTime` to AppBlueprintDetail\n returned by POST `/polaris/app_blueprint`.\n * Added new field `slaLastUpdateTime` to AppBlueprintDetail\n returned by `GET /app_blueprint/{id}`.\n * Added new field `slaLastUpdateTime` to AppBlueprintDetail\n returned by `PATCH /polaris/app_blueprint/{id}`.\n * Added new field `slaLastUpdateTime` to AppBlueprintExportSnapshotJobConfig\n returned by POST `/polaris/app_blueprint/snapshot/{id}/export`.\n * Added new field `slaLastUpdateTime` to AppBlueprintInstantRecoveryJobConfig\n returned by POST `/polaris/app_blueprint/snapshot/{id}/instant_recover`.\n * Added new field `slaLastUpdateTime` to AppBlueprintMountSnapshotJobConfig\n returned by POST `/polaris/app_blueprint/snapshot/{id}/mount`.\n * Added new field `slaLastUpdateTime` to AppBlueprintSummary\n returned by `GET /app_blueprint`.\n * Added new field `slaLastUpdateTime` to AwsAccountDetail\n returned by `PATCH /aws/account/dca/{id}`.\n * Added new field `slaLastUpdateTime` to AwsAccountDetail\n returned by `GET /aws/account/{id}`.\n * Added new field `slaLastUpdateTime` to AwsAccountDetail\n returned by `PATCH /aws/account/{id}`.\n * Added new field `slaLastUpdateTime` to AwsEc2InstanceDetail\n returned by `GET /aws/ec2_instance/{id}`.\n * Added new field `slaLastUpdateTime` to AwsEc2InstanceDetail\n returned by `PATCH /aws/ec2_instance/{id}`.\n * Added new field `slaLastUpdateTime` to FilesetDetail\n returned by POST `/fileset/bulk`.\n * Added new field `slaLastUpdateTime` to AwsEc2InstanceSummary\n returned by `GET /aws/ec2_instance`.\n * Added new field `slaLastUpdateTime` to AwsHierarchyObjectSummary\n returned by `GET /aws/hierarchy/{id}`.\n * Added new field `slaLastUpdateTime` to AwsHierarchyObjectSummary\n returned by `GET /aws/hierarchy/{id}/children`.\n * Added new field `slaLastUpdateTime` to AwsHierarchyObjectSummary\n returned by `GET /aws/hierarchy/{id}/descendants`.\n * Added new field `slaLastUpdateTime` to AwsHierarchyObjectSummary\n returned by `GET /organization/{id}/aws`.\n * Added new field `slaLastUpdateTime` to DataCenterDetail\n returned by `GET /vmware/data_center/{id}`.\n * Added new field `slaLastUpdateTime` to DataCenterSummary\n returned by `GET /vmware/data_center`.\n * Added new field `slaLastUpdateTime` to DataStoreDetail\n returned by `GET /vmware/datastore/{id}`.\n * Added new field `slaLastUpdateTime` to FolderDetail\n returned by `GET /folder/host/{datacenter_id}`.\n * Added new field `slaLastUpdateTime` to FolderDetail\n returned by `GET /folder/vm/{datacenter_id}`.\n * Added new field `slaLastUpdateTime` to FolderDetail\n returned by `GET /folder/{id}`.\n * Added new field `slaLastUpdateTime` to HostFilesetDetail\n returned by `GET /host_fileset/{id}`.\n * Added new field `slaLastUpdateTime` to HostFilesetShareDetail\n returned by `GET /host_fileset/share/{id}`.\n * Added new field `slaLastUpdateTime` to HostFilesetShareSummary\n returned by `GET /host_fileset/share`.\n * Added new field `slaLastUpdateTime` to HostFilesetSummary\n returned by `GET /host_fileset`.\n * Added new field `slaLastUpdateTime` to HypervClusterDetail\n returned by `GET /hyperv/cluster/{id}`.\n * Added new field `slaLastUpdateTime` to HypervClusterDetail\n returned by `PATCH /hyperv/cluster/{id}`.\n * Added new field `slaLastUpdateTime` to HypervClusterSummary\n returned by `GET /hyperv/cluster`.\n * Added new field `slaLastUpdateTime` to HypervHierarchyObjectSummary\n returned by `GET /hyperv/hierarchy/{id}`.\n * Added new field `slaLastUpdateTime` to HypervHierarchyObjectSummary\n returned by `GET /hyperv/hierarchy/{id}/children`.\n * Added new field `slaLastUpdateTime` to HypervHierarchyObjectSummary\n returned by `GET /hyperv/hierarchy/{id}/descendants`.\n * Added new field `slaLastUpdateTime` to HypervHierarchyObjectSummary\n returned by `GET /organization/{id}/hyperv`.\n * Added new field `slaLastUpdateTime` to HypervHostDetail\n returned by `GET /hyperv/host/{id}`.\n * Added new field `slaLastUpdateTime` to HypervHostDetail\n returned by `PATCH /hyperv/host/{id}`.\n * Added new field `slaLastUpdateTime` to HypervHostSummary\n returned by `GET /hyperv/host`.\n * Added new field `slaLastUpdateTime` to HypervScvmmDetail\n returned by `GET /hyperv/scvmm/{id}`.\n * Added new field `slaLastUpdateTime` to HypervScvmmDetail\n returned by `PATCH /hyperv/scvmm/{id}`.\n * Added new field `slaLastUpdateTime` to HypervScvmmSummary\n returned by `GET /hyperv/scvmm`.\n * Added new field `slaLastUpdateTime` to HypervVirtualMachineDetail\n returned by `GET /hyperv/vm/{id}`.\n * Added new field `slaLastUpdateTime` to HypervVirtualMachineDetail\n returned by `PATCH /hyperv/vm/{id}`.\n * Added new field `slaLastUpdateTime` to HypervVirtualMachineSummary\n returned by `GET /hyperv/vm`.\n * Added new field `slaLastUpdateTime` to ManagedHierarchyObjectSummary\n returned by `GET /hierarchy/{id}`.\n * Added new field `slaLastUpdateTime` to ManagedHierarchyObjectSummary\n returned by `GET /hierarchy/{id}/sla_conflicts`.\n * Added new field `slaLastUpdateTime` to ManagedVolumeSummary\n returned by `GET /managed_volume`.\n * Added new field `slaLastUpdateTime` to ManagedVolumeSummary\n returned by POST `/managed_volume`.\n * Added new field `slaLastUpdateTime` to ManagedVolumeSummary\n returned by `GET /managed_volume/{id}`.\n * Added new field `slaLastUpdateTime` to ManagedVolumeSummary\n returned by `PATCH /managed_volume/{id}`.\n * Added new field `slaLastUpdateTime` to ManagedVolumeSummary\n returned by `GET /organization/{id}/managed_volume`.\n * Added new field `slaLastUpdateTime` to MountDetail\n returned by `GET /vmware/vm/snapshot/mount/{id}`.\n * Added new field `slaLastUpdateTime` to NutanixHierarchyObjectSummary\n returned by `GET /nutanix/hierarchy/{id}`.\n * Added new field `slaLastUpdateTime` to NutanixHierarchyObjectSummary\n returned by `GET /nutanix/hierarchy/{id}/children`.\n * Added new field `slaLastUpdateTime` to NutanixHierarchyObjectSummary\n returned by `GET /nutanix/hierarchy/{id}/descendants`.\n * Added new field `slaLastUpdateTime` to NutanixHierarchyObjectSummary\n returned by `GET /organization/{id}/nutanix`.\n * Added new field `slaLastUpdateTime` to OracleDbDetail\n returned by `GET /oracle/db/{id}`.\n * Added new field `slaLastUpdateTime` to OracleDbDetail\n returned by `PATCH /oracle/db/{id}`.\n * Added new field `slaLastUpdateTime` to OracleDbSummary\n returned by `GET /oracle/db`.\n * Added new field `slaLastUpdateTime` to OracleHierarchyObjectSummary\n returned by `GET /oracle/hierarchy/{id}`.\n * Added new field `slaLastUpdateTime` to OracleHierarchyObjectSummary\n returned by `GET /oracle/hierarchy/{id}/children`.\n * Added new field `slaLastUpdateTime` to OracleHierarchyObjectSummary\n returned by `GET /oracle/hierarchy/{id}/descendants`.\n * Added new field `slaLastUpdateTime` to OracleHierarchyObjectSummary\n returned by `GET /organization/{id}/oracle`.\n * Added new field `slaLastUpdateTime` to OracleHostDetail\n returned by `GET /oracle/host/{id}`.\n * Added new field `slaLastUpdateTime` to OracleHostDetail\n returned by `PATCH /oracle/host/{id}`.\n * Added new field `slaLastUpdateTime` to OracleHostSummary\n returned by `GET /oracle/host`.\n * Added new field `slaLastUpdateTime` to OracleRacDetail\n returned by `GET /oracle/rac/{id}`.\n * Added new field `slaLastUpdateTime` to OracleRacDetail\n returned by `PATCH /oracle/rac/{id}`.\n * Added new field `slaLastUpdateTime` to OracleRacSummary\n returned by `GET /oracle/rac`.\n * Added new field `slaLastUpdateTime` to Snappable\n returned by POST `/polaris/failover/recovery_spec/upsert`.\n * Added new field `slaLastUpdateTime` to SnappableRecoverySpec\n returned by POST `/polaris/failover/recovery_spec/upsert`.\n * Added new field `slaLastUpdateTime` to SnappableRecoverySpecDetails\n returned by POST `/polaris/failover/recovery_spec/upsert`.\n * Added new field `slaLastUpdateTime` to StorageArrayHierarchyObjectSummary\n returned by `GET /organization/{id}/storage/array`.\n * Added new field `slaLastUpdateTime` to StorageArrayHierarchyObjectSummary\n returned by `GET /storage/array/hierarchy/{id}`.\n * Added new field `slaLastUpdateTime` to StorageArrayHierarchyObjectSummary\n returned by `GET /storage/array/hierarchy/{id}/children`.\n * Added new field `slaLastUpdateTime` to StorageArrayHierarchyObjectSummary\n returned by `GET /storage/array/hierarchy/{id}/descendants`.\n * Added new field `slaLastUpdateTime` to StorageArrayVolumeGroupDetail\n returned by POST `/storage/array_volume_group`.\n * Added new field `slaLastUpdateTime` to StorageArrayVolumeGroupDetail\n returned by `GET /storage/array_volume_group/{id}`.\n * Added new field `slaLastUpdateTime` to StorageArrayVolumeGroupDetail\n returned by `PATCH /storage/array_volume_group/{id}`.\n * Added new field `slaLastUpdateTime` to StorageArrayVolumeGroupSummary\n returned by `GET /storage/array_volume_group`.\n * Added new field `slaLastUpdateTime` to VcdClusterDetail\n returned by `GET /vcd/cluster/{id}`.\n * Added new field `slaLastUpdateTime` to VcdClusterDetail\n returned by `PATCH /vcd/cluster/{id}`.\n * Added new field `slaLastUpdateTime` to VcdClusterSummary\n returned by `GET /vcd/cluster`.\n * Added new field `slaLastUpdateTime` to VcdHierarchyObjectSummary\n returned by `GET /organization/{id}/vcd`.\n * Added new field `slaLastUpdateTime` to VcdHierarchyObjectSummary\n returned by `GET /vcd/hierarchy/{id}`.\n * Added new field `slaLastUpdateTime` to VcdHierarchyObjectSummary\n returned by `GET /vcd/hierarchy/{id}/children`.\n * Added new field `slaLastUpdateTime` to VcdHierarchyObjectSummary\n returned by `GET /vcd/hierarchy/{id}/descendants`.\n * Added new field `slaLastUpdateTime` to VcdVappDetail\n returned by `GET /vcd/vapp/{id}`.\n * Added new field `slaLastUpdateTime` to VcdVappDetail\n returned by `PATCH /vcd/vapp/{id}`.\n * Added new field `slaLastUpdateTime` to VcdVappSnapshotDetail\n returned by `GET /vcd/vapp/snapshot/{id}`.\n * Added new field `slaLastUpdateTime` to VcdVappSummary\n returned by `GET /vcd/vapp`.\n * Added new field `slaLastUpdateTime` to VmwareVmMountSummary\n returned by `GET /vmware/vm/snapshot/mount`.\n * Added new field `slaLastUpdateTime` to VolumeGroupDetail\n returned by `GET /volume_group/{id}`.\n * Added new field `slaLastUpdateTime` to VolumeGroupDetail\n returned by `PATCH /volume_group/{id}`.\n * Added new field `slaLastUpdateTime` to VolumeGroupSummary\n returned by `GET /volume_group`.\n * Added new field `slaLastUpdateTime` to VsphereCategory\n returned by `GET /vmware/vcenter/{id}/tag_category`.\n * Added new field `slaLastUpdateTime` to VsphereCategory\n returned by `GET /vmware/vcenter/tag_category/{tag_category_id}`.\n * Added new field `slaLastUpdateTime` to VsphereTag\n returned by `GET /vmware/vcenter/{id}/tag`.\n * Added new field `slaLastUpdateTime` to VsphereTag\n returned by `GET /vmware/vcenter/tag/{tag_id}`.\n * Added new Field `configuredSlaDomainType` to AppBlueprintDetail returned by\n `POST /polaris/app_blueprint`.\n * Added new Field `configuredSlaDomainType` to AppBlueprintDetail returned by\n `GET /app_blueprint/{id}`.\n * Added new Field `configuredSlaDomainType` to AppBlueprintDetail returned by\n `PATCH /polaris/app_blueprint/{id}`.\n * Added new Field `configuredSlaDomainType` to\n AppBlueprintExportSnapshotJobConfig returned by\n `POST /polaris/app_blueprint/snapshot/{id}/export`.\n * Added new Field `configuredSlaDomainType` to\n AppBlueprintInstantRecoveryJobConfig returned by\n `POST /polaris/app_blueprint/snapshot/{id}/instant_recover`.\n * Added new Field `configuredSlaDomainType` to\n AppBlueprintMountSnapshotJobConfig returned by\n `POST /polaris/app_blueprint/snapshot/{id}/mount`.\n * Added new Field `configuredSlaDomainType` to AppBlueprintSummary returned by\n `GET /app_blueprint`.\n * Added new Field `configuredSlaDomainType` to AwsAccountDetail returned by\n `PATCH /aws/account/dca/{id}`.\n * Added new Field `configuredSlaDomainType` to AwsAccountDetail returned by\n `GET /aws/account/{id}`.\n * Added new Field `configuredSlaDomainType` to AwsAccountDetail returned by\n `PATCH /aws/account/{id}`.\n * Added new Field `configuredSlaDomainType` to AwsEc2InstanceDetail returned\n by `GET /aws/ec2_instance/{id}`.\n * Added new Field `configuredSlaDomainType` to AwsEc2InstanceDetail returned\n by `PATCH /aws/ec2_instance/{id}`.\n * Added new Field `configuredSlaDomainType` to AwsEc2InstanceSummary returned\n by `GET /aws/ec2_instance`.\n * Added new Field `configuredSlaDomainType` to AwsHierarchyObjectSummary\n returned by `GET /aws/hierarchy/{id}`.\n * Added new Field `configuredSlaDomainType` to AwsHierarchyObjectSummary\n returned by `GET /aws/hierarchy/{id}/children`.\n * Added new Field `configuredSlaDomainType` to AwsHierarchyObjectSummary\n returned by `GET /aws/hierarchy/{id}/descendants`.\n * Added new Field `configuredSlaDomainType` to AwsHierarchyObjectSummary\n returned by `GET /organization/{id}/aws`.\n * Added new Field `configuredSlaDomainType` to DataCenterDetail returned by\n `GET /vmware/data_center/{id}`.\n * Added new Field `configuredSlaDomainType` to DataCenterSummary returned by\n `GET /vmware/data_center`.\n * Added new Field `configuredSlaDomainType` to DataStoreDetail returned by\n `GET /vmware/datastore/{id}`.\n * Added new Field `configuredSlaDomainType` to FilesetDetail returned by\n `POST /fileset/bulk`.\n * Added new Field `configuredSlaDomainType` to FolderDetail returned by\n `GET /folder/host/{datacenter_id}`.\n * Added new Field `configuredSlaDomainType` to FolderDetail returned by\n `GET /folder/vm/{datacenter_id}`.\n * Added new Field `configuredSlaDomainType` to FolderDetail returned by\n `GET /folder/{id}`.\n * Added new Field `configuredSlaDomainType` to HostFilesetDetail returned by\n `GET /host_fileset/{id}`.\n * Added new Field `configuredSlaDomainType` to HostFilesetShareDetail returned\n by `GET /host_fileset/share/{id}`.\n * Added new Field `configuredSlaDomainType` to HostFilesetShareSummary\n returned by `GET /host_fileset/share`.\n * Added new Field `configuredSlaDomainType` to HostFilesetSummary returned by\n `GET /host_fileset`.\n * Added new Field `configuredSlaDomainType` to HypervClusterDetail returned by\n `GET /hyperv/cluster/{id}`.\n * Added new Field `configuredSlaDomainType` to HypervClusterDetail returned by\n `PATCH /hyperv/cluster/{id}`.\n * Added new Field `configuredSlaDomainType` to HypervClusterSummary returned\n by `GET /hyperv/cluster`.\n * Added new Field `configuredSlaDomainType` to HypervHierarchyObjectSummary\n returned by `GET /hyperv/hierarchy/{id}`.\n * Added new Field `configuredSlaDomainType` to HypervHierarchyObjectSummary\n returned by `GET /hyperv/hierarchy/{id}/children`.\n * Added new Field `configuredSlaDomainType` to HypervHierarchyObjectSummary\n returned by `GET /hyperv/hierarchy/{id}/descendants`.\n * Added new Field `configuredSlaDomainType` to HypervHierarchyObjectSummary\n returned by `GET /organization/{id}/hyperv`.\n * Added new Field `configuredSlaDomainType` to HypervHostDetail returned by\n `GET /hyperv/host/{id}`.\n * Added new Field `configuredSlaDomainType` to HypervHostDetail returned by\n `PATCH /hyperv/host/{id}`.\n * Added new Field `configuredSlaDomainType` to HypervHostSummary returned by\n `GET /hyperv/host`.\n * Added new Field `configuredSlaDomainType` to HypervScvmmDetail returned by\n `GET /hyperv/scvmm/{id}`.\n * Added new Field `configuredSlaDomainType` to HypervScvmmDetail returned by\n `PATCH /hyperv/scvmm/{id}`.\n * Added new Field `configuredSlaDomainType` to HypervScvmmSummary returned by\n `GET /hyperv/scvmm`.\n * Added new Field `configuredSlaDomainType` to HypervVirtualMachineDetail\n returned by `GET /hyperv/vm/{id}`.\n * Added new Field `configuredSlaDomainType` to HypervVirtualMachineDetail\n returned by `PATCH /hyperv/vm/{id}`.\n * Added new Field `configuredSlaDomainType` to HypervVirtualMachineSummary\n returned by `GET /hyperv/vm`.\n * Added new Field `configuredSlaDomainType` to ManagedHierarchyObjectSummary\n returned by `GET /hierarchy/{id}`.\n * Added new Field `configuredSlaDomainType` to ManagedHierarchyObjectSummary\n returned by `GET /hierarchy/{id}/sla_conflicts`.\n * Added new Field `configuredSlaDomainType` to ManagedVolumeSummary returned\n by `GET /managed_volume`.\n * Added new Field `configuredSlaDomainType` to ManagedVolumeSummary returned\n by `POST /managed_volume`.\n * Added new Field `configuredSlaDomainType` to ManagedVolumeSummary returned\n by `GET /managed_volume/{id}`.\n * Added new Field `configuredSlaDomainType` to ManagedVolumeSummary returned\n by `PATCH /managed_volume/{id}`.\n * Added new Field `configuredSlaDomainType` to ManagedVolumeSummary returned\n by `GET /organization/{id}/managed_volume`.\n * Added new Field `configuredSlaDomainType` to MountDetail returned by\n `GET /vmware/vm/snapshot/mount/{id}`.\n * Added new Field `configuredSlaDomainType` to NutanixHierarchyObjectSummary\n returned by `GET /nutanix/hierarchy/{id}`.\n * Added new Field `configuredSlaDomainType` to NutanixHierarchyObjectSummary\n returned by `GET /nutanix/hierarchy/{id}/children`.\n * Added new Field `configuredSlaDomainType` to NutanixHierarchyObjectSummary\n returned by `GET /nutanix/hierarchy/{id}/descendants`.\n * Added new Field `configuredSlaDomainType` to NutanixHierarchyObjectSummary\n returned by `GET /organization/{id}/nutanix`.\n * Added new Field `configuredSlaDomainType` to OracleDbDetail returned by\n `GET /oracle/db/{id}`.\n * Added new Field `configuredSlaDomainType` to OracleDbDetail returned by\n `PATCH /oracle/db/{id}`.\n * Added new Field `configuredSlaDomainType` to OracleDbSummary returned by\n `GET /oracle/db`.\n * Added new Field `configuredSlaDomainType` to OracleHierarchyObjectSummary\n returned by `GET /oracle/hierarchy/{id}`.\n * Added new Field `configuredSlaDomainType` to OracleHierarchyObjectSummary\n returned by `GET /oracle/hierarchy/{id}/children`.\n * Added new Field `configuredSlaDomainType` to OracleHierarchyObjectSummary\n returned by `GET /oracle/hierarchy/{id}/descendants`.\n * Added new Field `configuredSlaDomainType` to OracleHierarchyObjectSummary\n returned by `GET /organization/{id}/oracle`.\n * Added new Field `configuredSlaDomainType` to OracleHostDetail returned by\n `GET /oracle/host/{id}`.\n * Added new Field `configuredSlaDomainType` to OracleHostDetail returned by\n `PATCH /oracle/host/{id}`.\n * Added new Field `configuredSlaDomainType` to OracleHostSummary returned by\n `GET /oracle/host`.\n * Added new Field `configuredSlaDomainType` to OracleRacDetail returned by\n `GET /oracle/rac/{id}`.\n * Added new Field `configuredSlaDomainType` to OracleRacDetail returned by\n `PATCH /oracle/rac/{id}`.\n * Added new Field `configuredSlaDomainType` to OracleRacSummary returned by\n `GET /oracle/rac`.\n * Added new Field `configuredSlaDomainType` to SlaConflictsSummary returned by\n `POST /hierarchy/bulk_sla_conflicts`.\n * Added new Field `configuredSlaDomainType` to Snappable returned by\n `POST /polaris/failover/recovery_spec/upsert`.\n * Added new Field `configuredSlaDomainType` to SnappableRecoverySpec returned\n by `POST /polaris/failover/recovery_spec/upsert`.\n * Added new Field `configuredSlaDomainType` to SnappableRecoverySpecDetails\n returned by `POST /polaris/failover/recovery_spec/upsert`.\n * Added new Field `configuredSlaDomainType` to\n StorageArrayHierarchyObjectSummary returned by\n `GET /organization/{id}/storage/array`.\n * Added new Field `configuredSlaDomainType` to\n StorageArrayHierarchyObjectSummary returned by\n `GET /storage/array/hierarchy/{id}`.\n * Added new Field `configuredSlaDomainType` to\n StorageArrayHierarchyObjectSummary returned by\n `GET /storage/array/hierarchy/{id}/children`.\n * Added new Field `configuredSlaDomainType` to\n StorageArrayHierarchyObjectSummary returned by\n `GET /storage/array/hierarchy/{id}/descendants`.\n * Added new Field `configuredSlaDomainType` to StorageArrayVolumeGroupDetail\n returned by `POST /storage/array_volume_group`.\n * Added new Field `configuredSlaDomainType` to StorageArrayVolumeGroupDetail\n returned by `GET /storage/array_volume_group/{id}`.\n * Added new Field `configuredSlaDomainType` to StorageArrayVolumeGroupDetail\n returned by `PATCH /storage/array_volume_group/{id}`.\n * Added new Field `configuredSlaDomainType` to StorageArrayVolumeGroupSummary\n returned by `GET /storage/array_volume_group`.\n * Added new Field `configuredSlaDomainType` to\n TriggerFailoverOnTargetDefinition returned by\n `PUT /polaris/failover/target/{id}/start`.\n * Added new Field `configuredSlaDomainType` to\n TriggerFailoverOnTargetDefinition returned by\n `PUT /polaris/failover/target/{id}/resume`.\n * Added new Field `configuredSlaDomainType` to\n UnmanagedObjectSummary returned by `GET /unmanaged_object`.\n * Added new Field `configuredSlaDomainType` to\n UpsertSnappableRecoverySpecResponse returned by\n `POST /polaris/failover/recovery_spec/upsert`.\n * Added new Field `configuredSlaDomainType` to VcdClusterDetail returned by\n `GET /vcd/cluster/{id}`.\n * Added new Field `configuredSlaDomainType` to VcdClusterDetail returned by\n `PATCH /vcd/cluster/{id}`.\n * Added new Field `configuredSlaDomainType` to VcdClusterSummary returned by\n `GET /vcd/cluster`.\n * Added new Field `configuredSlaDomainType` to VcdHierarchyObjectSummary\n returned by `GET /organization/{id}/vcd`.\n * Added new Field `configuredSlaDomainType` to VcdHierarchyObjectSummary\n returned by `GET /vcd/hierarchy/{id}`.\n * Added new Field `configuredSlaDomainType` to VcdHierarchyObjectSummary\n returned by `GET /vcd/hierarchy/{id}/children`.\n * Added new Field `configuredSlaDomainType` to VcdHierarchyObjectSummary\n returned by `GET /vcd/hierarchy/{id}/descendants`.\n * Added new Field `configuredSlaDomainType` to VcdVappDetail returned by\n `GET /vcd/vapp/{id}`.\n * Added new Field `configuredSlaDomainType` to VcdVappDetail returned by\n `PATCH /vcd/vapp/{id}`.\n * Added new Field `configuredSlaDomainType` to VcdVappSnapshotDetail returned\n by `GET /vcd/vapp/snapshot/{id}`.\n * Added new Field `configuredSlaDomainType` to VcdVappSummary returned by\n `GET /vcd/vapp`.\n * Added new Field `configuredSlaDomainType` to VmwareVmMountSummary returned\n by `GET /vmware/vm/snapshot/mount`.\n * Added new Field `configuredSlaDomainType` to VolumeGroupDetail returned by\n `GET /volume_group/{id}`.\n * Added new Field `configuredSlaDomainType` to VolumeGroupDetail returned by\n `PATCH /volume_group/{id}`.\n * Added new Field `configuredSlaDomainType` to VolumeGroupSummary returned by\n `GET /volume_group`.\n * Added new Field `configuredSlaDomainType` to VsphereCategory returned by\n `GET /vmware/vcenter/{id}/tag_category`.\n * Added new Field `configuredSlaDomainType` to VsphereCategory returned by\n `GET /vmware/vcenter/tag_category/{tag_category_id}`.\n * Added new Field `configuredSlaDomainType` to VsphereTag returned by\n `GET /vmware/vcenter/{id}/tag`.\n * Added new Field `configuredSlaDomainType` to VsphereTag returned by\n `GET /vmware/vcenter/tag/{tag_id}`.\n * Added a new optional query parameter `name` to\n `GET /user/{id}/organization`.\n * Added new field `hostLogRetentionHours` to OracleDbSummary returned by\n `GET /oracle/db`.\n * Added new field `isCustomRetentionApplied` to AppBlueprintSnapshotSummary\n returned by `GET /app_blueprint/{id}/snapshot`.\n * Added new field `isCustomRetentionApplied` to AppBlueprintSnapshotDetail\n returned by `GET /app_blueprint/snapshot/{id}` .\n * Added new field `isCustomRetentionApplied` to AwsEc2InstanceSummary returned\n by `GET /aws/ec2_instance`.\n * Added new field `isCustomRetentionApplied` to AwsEc2InstanceDetail returned\n by `GET /aws/ec2_instance/{id}`.\n * Added new field `isCustomRetentionApplied` to AwsEc2InstanceDetail returned\n by `PATCH /aws/ec2_instance/{id}`.\n * Added new field `isCustomRetentionApplied` to\n HypervVirtualMachineSnapshotSummary returned by\n `GET /hyperv/vm/{id}/snapshot`.\n * Added new field `isCustomRetentionApplied` to\n HypervVirtualMachineSnapshotDetail returned by\n `GET /hyperv/vm/snapshot/{id}`.\n * Added new field `isCustomRetentionApplied` to ManagedVolumeSnapshotSummary\n returned by `GET /managed_volume/{id}/snapshot`.\n * Added new field `isCustomRetentionApplied` to ManagedVolumeSnapshotSummary\n returned by `POST /managed_volume/{id}/end_snapshot`.\n * Added new field `isCustomRetentionApplied` to ManagedVolumeSnapshotDetail\n returned by `GET /managed_volume/snapshot/{id}`.\n * Added new field `isCustomRetentionApplied` to NutanixVmSnapshotSummary\n returned by `GET /nutanix/vm/{id}/snapshot`.\n * Added new field `isCustomRetentionApplied` to NutanixVmSnapshotDetail\n returned by `GET /nutanix/vm/snapshot/{id}`.\n * Added new field `isCustomRetentionApplied` to OracleDbSnapshotSummary\n returned by `GET /oracle/db/{id}/snapshot`.\n * Added new field `isCustomRetentionApplied` to OracleDbSnapshotDetail returned\n by `GET /oracle/db/snapshot/{id}`.\n * Added new field `isCustomRetentionApplied` to\n StorageArrayVolumeGroupSnapshotSummary returned by\n `GET /storage/array_volume_group/{id}/snapshot`.\n * Added new field `isCustomRetentionApplied` to\n StorageArrayVolumeGroupSnapshotDetail returned by\n `GET /storage/array_volume_group/snapshot/{id}`.\n * Added new field `isCustomRetentionApplied` to VcdVappSnapshotSummary returned\n by `GET /vcd/vapp/{id}/snapshot`.\n * Added new field `isCustomRetentionApplied` to VcdVappSnapshotDetail returned\n by `GET /vcd/vapp/snapshot/{id}`.\n * Added new field `isCustomRetentionApplied` to VolumeGroupSnapshotSummary\n returned by `GET /volume_group/{id}/snapshot`.\n * Added new field `isCustomRetentionApplied` to VolumeGroupSnapshotDetail\n returned by `GET /volume_group/snapshot/{id}`.\n * Added optional field `isQueuedSnapshot` to the response of\n GET `/managed_volume/{id}/snapshot`, GET `/managed_volume/snapshot/{id}`.\n and POST `/managed_volume/{id}/end_snapshot`.\n The field specifies if ManagedVolume snapshots are in queue to be stored\n as patch file.\n * Added new field `securityLevel` to `SnmpTrapReceiverConfig` object as\n optional input parameter for SNMPv3, which is used in\n `PATCH /cluster/{id}/snmp_configuration` and\n `GET /cluster/{id}/snmp_configuration`.\n * Added new field `advancedRecoveryConfigBase64` to `ExportOracleDbConfig`.\n and `MountOracleDbConfig` objects as optional input parameter\n during Oracle recovery.\n * Added new optional field `isRemote` to UnmanagedObjectSummary object, which\n is returned from a `GET /unmanaged_object` call.\n * Added new field `hostLogRetentionHours` to OracleRacDetail returned by\n `GET /oracle/rac/{id}` and `PATCH /oracle/rac/{id}`.\n * Added new field `hostLogRetentionHours` to OracleHostDetail returned by\n `GET /oracle/host/{id}` and `PATCH /oracle/host/{id}`.\n * Added new field `hostLogRetentionHours` to OracleDbDetail returned by\n `GET /oracle/db/{id}` and `PATCH /oracle/db/{id}`.\n * Added new field `snapshotFrequency` to `snapshotLocationRetentionInfo` field\n of `SnapshotRetentionInfo` field of AppBlueprintSnapshotSummary returned\n by `GET /app_blueprint/{id}/snapshot`.\n * Added new field `snapshotFrequency` to `snapshotLocationRetentionInfo` field\n of `SnapshotRetentionInfo` field of AppBlueprintSnapshotDetail returned by\n `GET /app_blueprint/snapshot/{id}` .\n * Added new field `snapshotFrequency` to `snapshotLocationRetentionInfo` field\n of `SnapshotRetentionInfo` field of AwsEc2InstanceSummary returned by\n `GET /aws/ec2_instance`.\n * Added new field `snapshotFrequency` to `snapshotLocationRetentionInfo` field\n of `SnapshotRetentionInfo` field of AwsEc2InstanceDetail returned by\n `GET /aws/ec2_instance/{id}`.\n * Added new field `snapshotFrequency` to `snapshotLocationRetentionInfo` field\n of `SnapshotRetentionInfo` field of AwsEc2InstanceDetail returned by\n `PATCH /aws/ec2_instance/{id}`.\n * Added new field `snapshotFrequency` to `snapshotLocationRetentionInfo` field\n of `SnapshotRetentionInfo` field of HypervVirtualMachineSnapshotSummary\n returned by `GET /hyperv/vm/{id}/snapshot`.\n * Added new field `snapshotFrequency` to `snapshotLocationRetentionInfo` field\n of `SnapshotRetentionInfo` field of HypervVirtualMachineSnapshotDetail\n returned by `GET /hyperv/vm/snapshot/{id}`.\n * Added new field `snapshotFrequency` to `snapshotLocationRetentionInfo` field\n of `SnapshotRetentionInfo` field of ManagedVolumeSnapshotSummary returned\n by `GET /managed_volume/{id}/snapshot`.\n * Added new field `snapshotFrequency` to `snapshotLocationRetentionInfo` field\n of `SnapshotRetentionInfo` field of ManagedVolumeSnapshotSummary returned by\n `POST /managed_volume/{id}/end_snapshot`.\n * Added new field `snapshotFrequency` to `snapshotLocationRetentionInfo` field\n of `SnapshotRetentionInfo` field of ManagedVolumeSnapshotDetail returned\n by `GET /managed_volume/snapshot/{id}`.\n * Added new field `snapshotFrequency` to `snapshotLocationRetentionInfo` field\n of `SnapshotRetentionInfo` field of NutanixVmSnapshotSummary returned by\n `GET /nutanix/vm/{id}/snapshot`.\n * Added new field `snapshotFrequency` to `snapshotLocationRetentionInfo` field\n of `SnapshotRetentionInfo` field of NutanixVmSnapshotDetail returned by\n `GET /nutanix/vm/snapshot/{id}`.\n * Added new field `snapshotFrequency` to `snapshotLocationRetentionInfo` field\n of `SnapshotRetentionInfo` field of OracleDbSnapshotSummary returned by\n `GET /oracle/db/{id}/snapshot`.\n * Added new field `snapshotFrequency` to `snapshotLocationRetentionInfo` field\n of `SnapshotRetentionInfo` field of OracleDbSnapshotDetail returned by\n `GET /oracle/db/snapshot/{id}`.\n * Added new field `snapshotFrequency` to `snapshotLocationRetentionInfo` field\n of `SnapshotRetentionInfo` field of StorageArrayVolumeGroupSnapshotSummary\n returned by `GET /storage/array_volume_group/{id}/snapshot`.\n * Added new field `snapshotFrequency` to `snapshotLocationRetentionInfo` field\n of `SnapshotRetentionInfo` field of StorageArrayVolumeGroupSnapshotDetail\n returned by `GET /storage/array_volume_group/snapshot/{id}`.\n * Added new field `snapshotFrequency` to `snapshotLocationRetentionInfo` field\n of `SnapshotRetentionInfo` field of VcdVappSnapshotSummary returned by\n `GET /vcd/vapp/{id}/snapshot`.\n * Added new field `snapshotFrequency` to `snapshotLocationRetentionInfo` field\n of `SnapshotRetentionInfo` field of VcdVappSnapshotDetail returned by\n `GET /vcd/vapp/snapshot/{id}`.\n * Added new field `snapshotFrequency` to `snapshotLocationRetentionInfo` field\n of `SnapshotRetentionInfo` field of VolumeGroupSnapshotSummary returned by\n `GET /volume_group/{id}/snapshot`.\n * Added new field `snapshotFrequency` to `snapshotLocationRetentionInfo` field\n of `SnapshotRetentionInfo` field of VolumeGroupSnapshotDetail returned by\n `GET /volume_group/snapshot/{id}`.\n * Added new field `SnapshotRetentionInfo` to AppBlueprintSnapshotSummary\n returned by `GET /app_blueprint/{id}/snapshot`.\n * Added new field `SnapshotRetentionInfo` to AppBlueprintSnapshotDetail\n returned by `GET /app_blueprint/snapshot/{id}` .\n * Added new field `SnapshotRetentionInfo` to AwsEc2InstanceSummary returned\n by `GET /aws/ec2_instance`.\n * Added new field `SnapshotRetentionInfo` to AwsEc2InstanceDetail returned\n by `GET /aws/ec2_instance/{id}`.\n * Added new field `SnapshotRetentionInfo` to AwsEc2InstanceDetail returned\n by `PATCH /aws/ec2_instance/{id}`.\n * Added new field `SnapshotRetentionInfo` to\n HypervVirtualMachineSnapshotSummary returned by\n `GET /hyperv/vm/{id}/snapshot`.\n * Added new field `SnapshotRetentionInfo` to\n HypervVirtualMachineSnapshotDetail returned by\n `GET /hyperv/vm/snapshot/{id}`.\n * Added new field `SnapshotRetentionInfo` to ManagedVolumeSnapshotSummary\n returned by `GET /managed_volume/{id}/snapshot`.\n * Added new field `SnapshotRetentionInfo` to ManagedVolumeSnapshotSummary\n returned by `POST /managed_volume/{id}/end_snapshot`.\n * Added new field `SnapshotRetentionInfo` to ManagedVolumeSnapshotDetail\n returned by `GET /managed_volume/snapshot/{id}`.\n * Added new field `SnapshotRetentionInfo` to NutanixVmSnapshotSummary\n returned by `GET /nutanix/vm/{id}/snapshot`.\n * Added new field `SnapshotRetentionInfo` to NutanixVmSnapshotDetail\n returned by `GET /nutanix/vm/snapshot/{id}`.\n * Added new field `SnapshotRetentionInfo` to OracleDbSnapshotSummary\n returned by `GET /oracle/db/{id}/snapshot`.\n * Added new field `SnapshotRetentionInfo` to OracleDbSnapshotDetail returned\n by `GET /oracle/db/snapshot/{id}`.\n * Added new field `SnapshotRetentionInfo` to\n StorageArrayVolumeGroupSnapshotSummary returned by\n `GET /storage/array_volume_group/{id}/snapshot`.\n * Added new field `SnapshotRetentionInfo` to\n StorageArrayVolumeGroupSnapshotDetail returned by\n `GET /storage/array_volume_group/snapshot/{id}`.\n * Added new field `SnapshotRetentionInfo` to VcdVappSnapshotSummary returned\n by `GET /vcd/vapp/{id}/snapshot`.\n * Added new field `SnapshotRetentionInfo` to VcdVappSnapshotDetail returned\n by `GET /vcd/vapp/snapshot/{id}`.\n * Added new field `SnapshotRetentionInfo` to VolumeGroupSnapshotSummary\n returned by `GET /volume_group/{id}/snapshot`.\n * Added new field `SnapshotRetentionInfo` to VolumeGroupSnapshotDetail\n returned by `GET /volume_group/snapshot/{id}`.\n * Added optional field `networkInterface` to `NetworkThrottleUpdate`. The\n field allows users to specify non standard network interfaces. This applies\n to the `PATCH /network_throttle/{id}` endpoint.\n * Added mandatory field `networkInterface` to `NetworkThrottleSummary`.\n This applies to the endpoints `GET /network_throttle` and\n `GET /network_throttle/{id}`.\n * Added endpoint `POST /cluster/{id}/manual_discover`, which allows\n the customer to manually input data that would be learned using\n mDNS discovery. Returns same output as discover.\n * `PATCH /cluster/{id}/snmp_configuration` will now use\n `SnmpConfigurationPatch` as a parameter.\n * Added optional field `user` to `SnmpTrapReceiverConfig`. The field\n specifies which user to use for SNMPv3 traps.\n * Added optional field `users` to `SnmpConfiguration`. The field contains\n usernames of users configured for SNMPv3.\n * Added two new models `SnmpUserConfig` to store user credentials and\n `SnmpConfigurationPatch`.\n * Added new endpoint `POST /role/authorization_query` to get authorizations\n granted to roles.\n * Added new endpoint `GET /role/{id}/authorization` to get authorizations\n granted to a role.\n * Added new endpoint `POST /role/{id}/authorization` to grant authorizations\n to a role.\n * Added new endpoint `POST /role/{id}/authorization/bulk_revoke` to revoke\n authorizations from a role.\n * Added optional field `recoveryInfo` to UnmanagedObjectSummary.\n * Added optional field `isRetentionLocked` to SlaInfo.\n The parameter indicates that the SLA Domain associated with the job is a\n Retention Lock SLA Domain.\n * Added optional field `legalHoldDownloadConfig` to\n `FilesetDownloadFilesJobConfig`,`HypervDownloadFileJobConfig`,\n `DownloadFilesJobConfig`,`ManagedVolumeDownloadFileJobConfig`,\n `NutanixDownloadFilesJobConfig`,`StorageArrayDownloadFilesJobConfig`,\n `VolumeGroupDownloadFilesJobConfig`.This is an optional argument\n containing a Boolean parameter to depict if the download is being\n triggered for Legal Hold use case. This change applies to\n /fileset/snapshot/{id}/download_files,\n /hyperv/vm/snapshot/{id}/download_file,\n /vmware/vm/snapshot/{id}/download_files,\n /managed_volume/snapshot/{id}/download_file,\n /nutanix/vm/snapshot/{id}/download_files,\n /storage/array_volume_group/snapshot/{id}/download_files and\n /volume_group/snapshot/{id}/download_files endpoints.\n * Added optional field isPlacedOnLegalHold to BaseSnapshotSummary.\n The Boolean parameter specifies whether the snapshot is placed under a\n Legal Hold.\n * Added new endpoint `GET /ods_configuration`.\n Returns the current configuration of on-demand snapshot handling.\n * Added new endpoint `PUT /ods_configuration`.\n Update the configuration of on-demand snapshot handling.\n * Added two new models `OdsConfigurationSummary`, `OdsPolicyOnPause` and a new\n enum `SchedulingType`.\n * Added `odsPolicyOnPause` field in `OdsConfigurationSummary` to include the\n policy followed by the on-demand snapshots, during an effective pause.\n * Added new enum field `schedulingType` in `OdsPolicyOnPause` to support\n deferring the on-demand snapshots during an effective pause.\n * Added optional query parameter `show_snapshots_legal_hold_status` to\n `GET /archive/location` endpoint, indicating if `isLegalHoldSnapshotPresent`.\n field should be populated in response.\n * Added storage array volume group asynchronous request status endpoint\n `GET /storage/array_volume_group/request/{id}`. Request statuses for\n storage array volume groups which previously used\n `/storage/array/request/{id}` must now use this new endpoint.\n * Added forceFull parameter to the properties of patch volume group object\n to permit forcing a full snapshot for a specified volume group.\n * Added `isDcaAccountInstance` field to `AwsEc2InstanceSummary` to indicate\n whether the EC2 instance belongs to a DCA account. This impacts the endpoints\n `GET /aws/ec2_instance` and `GET /aws/ec2_instance/{id}`.\n * Added `encryptionKeyId` as an optional field in CreateCloudInstanceRequest\n definition used in the on-demand API conversion API `/cloud_on/aws/instance`.\n to support KMS encryption for CloudOn conversion in AWS.\n * Added new endpoint `GET /job/{id}/child_job_instance`.\n Returns the child job instances (if any) spawned by the given parent job\n instance. This endpoint requires a support token to access.\n * Updated `ArchivalLocationSummary` returned by `GET /archive/location`.\n endpoint to include the `isConsolidationEnabled` field, to indicate\n if consolidation is enabled for the given archival location.\n * Changed `encryptionPassword` parameter to optional in\n `NfsLocationCreationDefinition` to support creating NFS archival location\n without encryption via `POST /archive/nfs`.\n * Added an optional parameter `disabledEncryption` to\n `NfsLocationCreationDefinition` with a default value of false, to enable or\n disable encryption via `POST /archive/nfs`.\n * Added a new model `ValidationResponse` and REST API endpoints\n `/cloud_on/validate/instantiate_on_cloud` and\n `/cloud_on/validate/cloud_image_conversion` for validation of cloud\n conversion.\n * Added `sortBy` and `sortOrder` parameters to `GET /hyperv/vm/snapshot/mount`.\n to allow sorting of Hyper-V mounts.\n Added the enum `HypervVirtualMachineMountListSortAttribute`, defining which\n properties of Hyper-V mounts are sortable.\n * Added an optional field `shouldApplyToExistingSnapshots` in\n `SlaDomainAssignmentInfo` to apply the new SLA configuration to existing\n snapshots of protected objects.\n * Added a new optional field `isOracleHost` to `HostRegister` in\n `POST /host/bulk` and `HostUpdate` in `PATCH /host/bulk` to indicate if we\n should discover Oracle information during registration and host refresh.\n * Added a new model `NutanixVirtualDiskSummary` that is returned by\n `GET /nutanix/vm/{id}` to include the disks information for a Nutanix\n virtual machine.\n * Added mandatory field `pendingSnapshot` to `SystemStorageStats`, which is\n returned by `GET /stats/system_storage`.\n * Added optional isIsilonChangelistEnabled in the NasBaseConfig and NasConfig.\n NasBaseConfig is returned as part of HostSummary, which is returned by the\n `Get /host/envoy` and `Get /host` endpoints. NasConfig is used by\n HostRegister and HostUpdate. The HostRegister is used by the\n `Post /host/bulk` endpoint and the HostUpdate is used by the\n `PATCH /host/bulk` endpoint.\n * Added a new model `HostShareParameters`. This model has two fields,\n isNetAppSnapDiffEnabled and isIsilonChangelistEnabled. The\n isNetAppSnapDiffEnabled is a Boolean value that specifies whether the\n SnapDiff feature of NetApp NAS is used to back up the NAS share. The\n isIsilonChangelistEnabled is a Boolean value that specifies whether\n the Changelist feature of Isilon NAS is used to back up the NAS share.\n * Added optional field `HostShareParameters` in `HostFilesetShareSummary`,\n `HostFilesetShareDetail` and `HostShareDetail`. The HostShareDetail impacts\n the endpoints `Get /host/share` and `Post /host/share`. The\n `HostFilesetShareDetail` impacts the endpoint `Get /host_fileset/share/{id}`.\n . The HostFilesetShareSummary impacts the endpoint\n `Get /host_fileset/share`.\n * Added `isInVmc` in `GET /vcd/vapp/{id}`, and `PATCH /vcd/vapp/{id}`.\n to return whether the virtual machine is in a VMC setup.\n * Added new endpoint `GET /vmware/hierarchy/{id}/export`. Returns the\n VmwareHierarchyInfo object with the given ID.\n * Added optional field `platformDetails` to `PlatformInfo`, which is returned\n by `GET /cluster/{id}/platforminfo`.\n * Added optional field `cpuCount` to `PlatformInfo`, which is returned by\n `GET /cluster/{id}/platforminfo`.\n * Added optional field `ramSize` to `PlatformInfo`, which is returned by\n `GET /cluster/{id}/platforminfo`.\n * Added new value `RangeInTime` to `RecoveryPointType` enum, which is used in\n the `ReportTableRequest` object for the POST `/report/{id}/table` and POST\n `/report/data_source/table` endpoints.\n * Added the optional field `shouldForceFull` to `MssqlDbUpdate` object,\n which is referred by `MssqlDbUpdateId`, which is referred as the\n body parameter of `PATCH /mssql/db/bulk`.\n\n ### Changes to Internal API in Rubrik version 5.1.1\n ## Breaking changes:\n * Changed response code of a successful\n `POST /managed_volume/{id}/begin_snapshot` API from 201 to 200.\n\n ### Changes to Internal API in Rubrik version 5.1.0\n ## Breaking changes:\n * Changed response type of percentInCompliance and percentOutOfCompliance\n in ComplianceSummary to double.\n * Renamed new enum field `MissedSnapshots` to `MissedLocalSnapshots`.\n and `LastSnapshot` to `LatestLocalSnapshot`, in the\n following properties:\n measure property in ChartSummary, column property in TableSummary,\n and sortBy property in ReportTableRequest.\n * Renamed effectiveThroughput to throughput in EventSeriesMonitoredJobSummary.\n * Renamed realThroughput to throughput in EventSeriesSummary.\n * Updated response of GET /event_series/{id} to remove effectiveThroughput.\n * Renamed paths `/storage/array/volume/group` to `/storage/array_volume_group`.\n * Renamed the field cassandraSetup in ReplaceNodeStatus to metadataSetup\n * Renamed the field cassandraSetup in RecommisionNodeStatus to metadataSetup\n * Renamed the field cassandraSetup in AddNodesStatus to metadataSetup\n * Renamed the field cassandraSetup in ClusterConfigStatus to metadataSetup\n * Renamed the field removeCassandra in RemoveNodeStatus to removeMetadatastore\n for the GET /cluster/{id}/remove_node endpoint.\n * Moved the `GET /blackout_window` endpoint from internal to V1.\n * Moved the `PATCH /blackout_window` endpoint from internal to V1.\n * Removed endpoint POST /report/global_object endpoint.\n /report/data_source/table can be used to get the same information.\n * Made accessKey optional in ObjectStoreLocationDetail as accessKey is not\n defined in Cross Account Role Based locations. Also made accessKey required\n again in ObjectStoreLocationDefinition.\n * Removed `progressPercentage` from `EventSeriesMonitoredJobSummary` object.\n * Removed endpoint `POST cluster-id-security-password-strength` since it is\n no longer used at bootstrap.\n * Moved the GET `/mssql/hierarchy/{id}/descendants` and\n GET `/mssql/hierarchy/{id}/children` endpoints from internal to v1.\n\n ## Feature Additions/improvements:\n * GET POST /cluster/{id}/node now accepts an optional encryption\n password in the encryptionPassword field.\n * GET /node_management/replace_node now accepts an optional encryption\n password in the encryptionPassword field.\n * Added optional field `shouldSkipScheduleRecoverArchivedMetadataJob` to\n the body parameter of `POST /archive/object_store/reader/connect`, to\n determine whether to schedule the archival recovery job.\n When the value is 'false,' the recovery job is scheduled normally.\n When the value is 'true,' the recovery job is not scheduled.\n The default behavior is to schedule the recovery job.\n * Added mandatory field `cdp` to SystemStorageStats.\n * Added optional field `agentStatus` to NutanixHierarchyObjectSummary.\n The field indicates whether a Rubrik backup agent is registered to the\n Nutanix object.\n * Added optional field `shouldUseAgent` to `RestoreFilesJobConfig`.\n in `POST /vmware/vm/snapshot/{id}/restore_files` to specify\n whether to use Rubrik Backup Service to restore files. Default value is true.\n * GET /managed_object/bulk/summary and GET\n /managed_object/{managed_id}/summary no longer include archived objects\n with no unexpired snapshots in their results.\n * Added new required Boolean field `isDbLocalToTheCluster` to\n `OracleDbSummary` and `OracleDbDetail`.\n * Added optional field `awsAccountId` to ObjectStoreLocationSummary.\n * Added optional field `shouldRecoverSnappableMetadataOnly` to all the\n reader location connect definitions.\n * Added new enum value `ArchivalComplianceStatus` to the following properties:\n attribute property in ChartSummary and column property in TableSummary\n * Added new enum fields `ArchivalInComplianceCount`,\n `ArchivalNonComplianceCount` and `MissedArchivalSnapshots` to the\n following properties:\n measure property in ChartSummary, column property in TableSummary,\n and sortBy property in ReportTableRequest.\n * GET /managed_object/bulk/summary and GET\n /managed_object/{managed_id}/summary will always include the correct relic\n status for hosts and their descendants.\n * Added field `isLocked` to PrincipalSummary.\n * Added optional query parameter `snappableStatus` to /vmware/data_center and\n /vmware/host. This parameter enables a user to fetch the set of protectable\n objects from the list of objects visible to that user.\n * Added optional field `archivalComplianceStatus` to RequestFilters\n * Added optional field `archivalComplianceStatus` to FilterSummary\n * Added optional field `alias` to HostSummary, HostRegister, and HostUpdate\n schemas. This field will allow the user to specify an alias for each host\n which can be used for search.\n * Added optional field `subnet` to ManagedVolumeExportConfig\n * Added optional field `status` to oracle/hierarchy/{id}/children\n * Added optional field `status` to oracle/hierarchy/{id}/descendants\n * Added optional field `status` to hyperv/hierarchy/{id}/children\n * Added optional field `status` to hyperv/hierarchy/{id}/descendants\n * Added optional field `numNoSla` to ProtectedObjectsCount\n * Added optional field `numDoNotProtect` to ProtectedObjectsCount\n * Added optional field `limit`, `offset`, `sort_by`, `sort_order` to\n /node/stats\n * Added optional field encryptionAtRestPassword to configure password-based\n encryption for an edge instance.\n * Added new endpoint GET /report/data_source/{data_source_name}/csv.\n * Added new endpoint POST /report-24_hour_complianace_summary.\n * Added new endpoint POST /report/data-source/{data_source_name} to get\n columns directly from report data source.\n * Added optional field compliance24HourStatus to RequestFilters object.\n * Added the `port` optional field to QstarLocationDefinition. The `port` field\n enables a user to specify the server port when adding a new location or\n editing an existing location.\n * Added optional field archivalTieringSpec to ArchivalSpec and ArchivalSpecV2\n to support archival tiering. This enables the user to configure either\n Instant Tiering or Smart Tiering (with a corresponding minimum accessible\n duration) on an SLA domain with archival configured to an Azure archival\n location.\n * Updated endpoints /vcd/vapp, /oracle/db and /aws/ec2_instance\n to have a new optional query paramter, indicating if backup task information\n should be included.\n * Added optional field logConfig to SlaDomainSummaryV2, SlaDomainDefinitionV2\n and SlaDomainPatchDefintionV2 to support CDP (ctrlc). The parameters\n distinguish SLAs with CDP enabled from SLAs with CDP disabled, and enable\n users to specify log retention time. The field also provides an optional\n frequency parameter whhich can be used by Oracle and SQL Server log backups.\n * Added optional field logRetentionLimit to ReplicationSpec to support\n CDP replication. The field gives the retention limit for logs at the\n specified location.\n * Moved the `GET /vmware/compute_cluster` endpoint from internal to V1.\n * Moved the `GET /vmware/compute_cluster/{id}` endpoint from internal to V1.\n * Changed the existing `PATCH mssql/db/bulk` endpoint to return an\n unprotectable reason as a string in the `unprotectableReason` field instead\n of a JSON struct.\n * Added optional field `kmsMasterKeyId` and changed the existing field\n `pemFileContent` to optional field in `DcaLocationDefinition`.\n * Added new optional field `enableHardlinkSupport` to FilesetSummary and\n FilesetCreate in `POST /fileset`, \"GET /fileset\" and \"PATCH /fileset/{id}\"\n endpoints to enable recognition and deduplication of hardlinks in\n fileset backup.\n * Added optional query parameter to `GET /archive/location` endpoint,\n indicating if `isRetentionLockedSnapshotProtectedPresent` field should\n be populated in response.\n * Added continuous data protection state for each VMware virtual machine\n * Added new endpoint `PUT /polaris/archive/proxy_setting`.\n * Added new endpoint `GET /polaris/archive/proxy_setting/{id}`.\n * Added new endpoint `DELETE /polaris/archive/proxy_setting/{id}`.\n * Added new endpoint `PUT /polaris/archive/aws_compute_setting`.\n * Added new endpoint `GET /polaris/archive/aws_compute_setting/{id}`.\n * Added new endpoint `DELETE /polaris/archive/aws_compute_setting/{id}`.\n * Added new endpoint `PUT /polaris/archive/azure_compute_setting`.\n * Added new endpoint `GET /polaris/archive/azure_compute_setting/{id}`.\n * Added new endpoint `DELETE /polaris/archive/azure_compute_setting/{id}`.\n * Added new endpoint `PUT /polaris/archive/aws_iam_location`.\n * Added new endpoint `GET /polaris/archive/aws_iam_location/{id}`.\n * Added new endpoint `PUT /polaris/archive/azure_oauth_location`.\n * Added new endpoint `GET /polaris/archive/azure_oauth_location/{id}`.\n * Added new endpoint `PUT /polaris/archive/aws_iam_customer_account`.\n * Added new endpoint `GET /polaris/archive/aws_iam_customer/{id}`.\n * Added new endpoint `DELETE /polaris/archive/aws_iam_customer/{id}`.\n * Added new endpoint `PUT /polaris/archive/azure_oauth_customer`.\n * Added new endpoint `GET /polaris/archive/azure_oauth_customer/{id}`.\n * Added new endpoint `DELETE /polaris/archive/azure_oauth_customer/{id}`.\n * Updated `ArchivalLocationSummary` returned by `GET /archive/location`.\n endpoint to include `currentState` field, to indicate whether the archival\n location is connected or temporarily disconnected.\n * Updated `ArchivalLocationSummary` returned by `GET /archive/location`.\n endpoint to include `isComputeEnabled` field, to indicate whether the\n archival location has cloud compute enabled.\n * Added optional field `cloudStorageTier` to `BaseSnapshotSummary`, to indicate\n the current storage tier of the archived copy of a snapshot.\n * Added endpoint `PUT /polaris/archive/aws_iam_location/reader_connect`.\n to connect as a reader to an IAM based AWS archival location.\n * Added endpoint `PUT /polaris/archive/azure_oauth_location/reader_connect`.\n to connect as a reader to an OAuth based Azure archival location.\n * Added endpoint `POST polaris/archive/location/{id}/reader/promote`.\n to promote the current cluster to be the owner of a specified IAM based AWS\n archival location that is currently connected as a reader location.\n * Added endpoint `POST polaris/archive/location/{id}/reader/refresh`.\n to sync the current reader cluster with the contents on the IAM based AWS\n archival location.\n * Added effectiveSlaDomainName and effectiveSlaDomainSourceId fields\n to `GET /vmware/vcenter/{id}/tag_category` response object.\n * Added effectiveSlaDomainName and effectiveSlaDomainSourceId fields\n to `GET /vmware/vcenter/{id}/tag` response object.\n * Added continuous data protection status for reporting.\n * Added optional field `localCdpStatus` to the following components:\n ChartSummary, TableSummary, ReportTableRequest, RequestFilters and\n FilterSummary.\n * Added `ReportSnapshotIndexState` and `ReportObjectIndexType` to\n `/internal_report_models/internal/definitions/enums/internal_report.yml`.\n * Added optional field `latestSnapshotIndexState` and `objectIndexType` to\n the following components:\n TableSummary, ReportTableRequest, RequestFilters and FilterSummary.\n * Added 24 hour continuous data protection healthy percentage for reporting.\n * Added optional field `PercentLocal24HourCdpHealthy` to the following\n components: TableSummary, ReportTableRequest.\n * Added optional field `replicas` to MssqlHierarchyObjectSummary.\n * Added optional field `hosts` to MssqlHierarchyObjectSummary.\n * Added continuous data protection local log storage size and local throughput\n consumption for reporting.\n * Added optional fields `localCdpThroughput` and `localCdpLogStorage` to the\n following components: ChartSummary, TableSummary and ReportTableRequest.\n * Added optional field requestExclusionFilters to ReportTableRequest.\n * Added an optional field to ManagedVolumeSummary to retrieve the associated\n subnet.\n * Added optional field isEffectiveSlaDomainRetentionLocked to Snappable.\n The parameter depicts if the effective SLA domain for the snappable is\n a Retention Lock SLA Domain.\n * Updated the set of possible continuous data protection statuses for each\n VmwareVirtualMachine.\n * Added the optional field isEffectiveSlaDomainRetentionLocked to\n FilesetSummary. The field is a Boolean that specifies whether the effective\n SLA Domain of a fileset is retention locked.\n * Added optional field iConfiguredSlaDomainRetentionLocked to SlaAssignable.\n The parameter depicts if the configured SLA domain for the object is a\n Retention Lock SLA Domain.\n * Updated `ArchivalLocationSummary` returned by `GET /archive/location`.\n endpoint to include the `isTieringSupported` field, to indicate\n whether a given archival location supports tiering.\n * Added continuous data protection replication status for reporting.\n * Added CdpReplicationStatus as an optional field to the TableSummary and\n ReportTableRequest components.\n * Added optional CdpReplicationStatus field to RequestFilters and\n FiltersSummary.\n * Added optional field isEffectiveSlaDomainRetentionLocked to\n SearchItemSummary. The Boolean parameter specifies whether the effective\n SLA Domain for the search item is a Retention Lock SLA Domain.\n * Updated `OracleMountSummary` returned by GET /oracle/db/mount\n endpoint to include the isInstantRecovered field, to indicate\n whether the mount was created during an Instant Recovery or Live Mount.\n * Added optional field isEffectiveSlaDomainRetentionLocked to\n ManagedObjectSummary. The Boolean parameter specifies whether the effective\n SLA Domain for the search item is a Retention Lock SLA Domain.\n * Added optional field `isRetentionSlaDomainRetentionLocked` to\n UnmanagedSnapshotSummary. The parameter indicates that the retention SLA\n Domain associated with the snapshot is a Retention Lock SLA Domain.\n * Added optional field `isSlaRetentionLocked` to EventSeriesSummary.\n The parameter indicates that the SLA Domain associated with the event\n series is a Retention Lock SLA Domain.\n * Updated `ArchivalLocationSummary` returned by `GET /archive/location`.\n endpoint to include the `isConsolidationEnabled` field, to indicate\n if consolidation is enabled for the given archival location.\n * Added the `hasUnavailableDisks` field to `NodeStatus` to indicate whether a\n node has unavailable (failed or missing) disks. This change affects the\n endpoints `GET /cluster/{id}/node`, `GET /node`, `GET /node/{id}`, `GET\n /node/stats`, and `GET /node/{id}/stats`.\n * Added optional NAS vendor type to the HostShareDetail\n This change affectes the endpoints `Get /host/share`, `Post /host/share` and\n `Get /host/share/{id}`.\n * Added optional isSnapdiffEnabled in the NasBaseConfig and NasConfig\n NasBaseConfig is returned as part of HostSummary, which is returned by the\n `Get /host/envoy` and `Get /host` endpoints. NasConfig is used by\n HostRegister and HostUpdate. The HostRegister field is used by the\n `Post /host/bulk` endpoint and the HostUpdate is field used by the\n `PATCH /host/bulk` endpoint.\n * Added optional snapdiffUsed in the FilesetSnapshotSummary\n The FilesetSnapshotSummary is used by FilesetDetail and\n FilesetSnapshotDetail. This change affects the endpoints `Post\n /fileset/bulk`, `Get /host_fileset/share/{id}` and\n `Get /fileset/snapshot/{id}`.\n\n ### Changes to Internal API in Rubrik version 5.0.4\n ## Feature Additions/improvements:\n * Added objectState to FilterSummary which is part of body parameter of\n PATCH/report/{id}\n * Added objectState to RequestFilters which is part of body parameter of\n POST /report/data_source/table\n\n ### Changes to Internal API in Rubrik version 5.0.3\n ## Breaking changes:\n * Removed fields 'virtualMedia' and 'ssh' from IpmiAccess and\n IpmiAccessUpdate.\n\n ## Feature Additions/improvements:\n * Added a new optional field 'oracleQueryUser' to HostRegister, HostUpdate\n and HostDetail objects, for setting the Oracle username for account with\n query privileges on the host. This applies to the following endpoints:\n `POST /host/bulk`, `PATCH /host/{id}`, and `GET /host/{id}`.\n * Added a field `affectedNodeIds` to the `SystemStatus` object. This object is\n returned by `GET /cluster/{id}/system_status`.\n * Made `nodeId` a required field of the `DiskStatus` object. This object, or\n an object containing this object, is returned by the following endpoints:\n `GET /cluster/{id}/disk`, `PATCH /cluster/{id}/disk/{disk_id}`, and\n `GET /node/{id}`.\n\n ### Changes to Internal API in Rubrik version 5.0.2\n ## Feature Additions/improvements:\n * Added an optional fields `subnet` to `ManagedVolumeSummary` to retrieve the associated\n subnet.\n * Added `tablespaces` field in `OracleDbSnapshotSummary` to include the list\n of tablespaces in the Oracle database snapshot.\n * Added new endpoint `POST /hierarchy/bulk_sla_conflicts`.\n * Added optional field `limit`, `offset`, `sort_by`, `sort_order` to\n `GET /node/stats`.\n * Added optional field `numNoSla` to `ProtectedObjectsCount`.\n * Added optional field `numDoNotProtect` to `ProtectedObjectsCount`.\n * Introduced optional field `logicalSize` to `VirtualMachineDetail`. This\n field gives the sum of logical sizes of all the disks in the virtual\n machine.\n * Added optional fields `nodeIds`, `slaId`, `numberOfRetries`, and\n `isFirstFullSnapshot` to the response of `GET /event_series/{id}`.\n * Added `SapHanaLog` tag in `applicationTag` field of `ManagedVolumeConfig`.\n for SAP HANA log managed volumes.\n * Added required field `dbSnapshotSummaries` in `OracleRecoverableRange` to include\n the list of database snapshots in each Oracle recoverable range.\n * Added field `isOnline` to MssqlDbSummary and changed `hasPermissions` to\n required field.\n * Added `DbTransactionLog` tag, in applicationTag field of\n `ManagedVolumeConfig`, for generic log managed volumes. ApplicationTag has\n to be specified in the request field of POST /managed_volume.\n\n ### Changes to Internal API in Rubrik version 5.0.1\n ## Breaking changes:\n * Removed `GET/POST /smb/enable_security` endpoints.\n * Changed the `objectId` type in `EventSeriesMonitoredJobSummary` and\n `EventSeriesSummary` to a user-visible ID instead of a simple ID.\n * Updated endpoint `POST /smb/domain` to accept a list of domain controllers.\n * Removed endpoint `POST /report/global_object`.\n * Added optional field `kmsMasterKeyId` and changed the existing field\n `pemFileContent` to optional field in `DcaLocationDefinition`.\n * Removed `progressPercentage` from `EventSeriesMonitoredJobSummary` object.\n\n ## Feature Additions/improvements:\n * Updated `ArchivalLocationSummary` returned by `GET /archive/location`.\n endpoint to include `currentState` field, to indicate whether the archival\n location is connected or temporarily disconnected.\n * Added optional field `subnet` to ManagedVolumeExportConfig.\n * Added the`PUT /smb/config` endpoint to manage SMB configuration.\n * Added the following two endpoints.\n - `GET /stats/per_vm_storage`.\n - `GET /stats/per_vm_storage/{vm_id}`.\n * Added optional field `isStickySmbService` to the response of\n `GET /smb/domain` and `POST /smb/domain`.\n * Added new endpoint `GET /report/data_source/{data_source_name}/csv`.\n * Added new endpoint `POST /report_24_hour_complianace_summary`.\n * Added new endpoint `POST /report/data-source/{data_source_name}` to get\n columns directly from report data source.\n * Added new report API endpoints:\n - `GET /report/summary/physical_storage_time_series`.\n - `GET /report/summary/average_local_growth_per_day`.\n * Added `GET /node/stats` which returns stats for all nodes.\n * Added `GET /cluster/{id}/security/password/zxcvbn` to return\n the enabled or disabled status of ZXCVBN validation for new passwords.\n * Added `POST /cluster/{id}/security/password/zxcvbn` to toggle\n ZXCVBN validation for new passwords.\n\n ### Changes to Internal API in Rubrik version 5.0.0\n ## Breaking changes:\n * Removed `/user_notification` endpoints.\n * Added `rawName` field in `ArchivalLocationSummary`, which contains the\n raw name of the archival location.\n * Removed `shareType` from config field in PATCH /managedvolume request.\n * Changed `/cluster/me/ntp_server` endpoint to accept symmetric keys\n and the corresponding hashing algorithm.\n * Removed `/job/type/prune_job_instances` endpoint.\n * Removed `/kmip/configuration` endpoint.\n * Removed `/session/api_token` endpoint.\n * Added `subnet` field in `ManagedVolumeConfig`, which specifies an outgoing\n VLAN interface for a Rubrik node. This is a required value when creating a\n managed volume on a Rubrik node that has multiple VLAN interfaces.\n * Removed the `VolumeGroupVolumeSummary`, and replaced it with\n `HostVolumeSummary`.\n * Removed `volumeIdsIncludedInSnapshots` from `VolumeGroupDetail`.\n * Added new optional fields `mssqlCbtEnabled`, `mssqlCbtEffectiveStatus`,\n `mssqlCbtDriverInstalled`, `hostVfdEnabled` and `hostVfdDriverState` to\n GET /host/{id} response.\n * Responses for `/cluster/{id}/dns_nameserver` and\n `/cluster/{id}/dns_search_domain` changed to be array of strings.\n * Added new required field `language` in `UserPreferencesInfo` for\n GET /user/{id}/preferences and PATCH /user/{id}/preferences\n * Added new field `missedSnapshotTimeUnits` in `MissedSnapshot`.\n * Removed `localStorage` and `archiveStorage` from `UnmanagedSnapshotSummary`.\n * Moved the `Turn on or off a given AWS cloud instance` endpoint from PATCH of\n `/cloud_on/aws/instance` to PATCH of `/cloud_on/aws/instance/{id}/cloud_vm`.\n Also removed the `id` field from the definition of `CloudInstanceUpdate`.\n * Moved the `Turn on or off a given Azure cloud instance` endpoint from PATCH\n of `/cloud_on/azure/instance` to PATCH of\n `/cloud_on/azure/instance/{id}/cloud_vm`. Also removed the `id` field from\n the definition of `CloudInstanceUpdate`.\n * Moved the `Delete a given AWS cloud instance` endpoint from DELETE of\n `/cloud_on/aws/instance/{id}` to DELETE of\n `/cloud_on/aws/instance/{id}/cloud_vm`.\n * Moved the `Delete a given Azure cloud instance` endpoint from DELETE of\n `/cloud_on/azure/instance/{id}` to DELETE of\n `/cloud_on/azure/instance/{id}/cloud_vm`.\n * Modified the existing endpoint DELETE `/cloud_on/aws/instance/{id}` to\n remove entry of a given AWS cloud instance instead of terminating the\n instance.\n * Modified the existing endpoint DELETE `/cloud_on/azure/instance/{id}` to\n remove entry of a given Azure cloud instance instead of terminating the\n instance.\n * Removed `/job/type/job-schedule_gc_job_start_time_now` endpoint. Use\n endpoint POST `/job/type/garbageCollection` to schedule a GC job to\n run now.\n * Removed `config` parameter from `/job/type/garbageCollection`.\n * Added optional parameter `jobInstanceId` to `EventSummary`.\n * Added `jobInstanceId` as a new optional query parameter for\n GET /event_series/{id}/status endpoint.\n * Modified the endpoint GET /event_series/status to a POST and changed the\n input parameter to a request body of type `EventSeriesDetail`.\n * Modified the endpoint PATCH /replication/target/{id} to take a request body\n of type ReplicationTargetUpdate instead of ReplicationTargetDefinition.\n * Added Discovery EventType.\n * Added `name` and deprecated `hostname` in `HostSummary` and `HostDetail`.\n response.\n * Added `isDeleted` and deprecated `isArchived` in MssqlDbReplica response.\n * Removed `GET /stats/cloud_storage` endpoint.\n * Removed DELETE /oracle/db/{id} endpoint to delete an Oracle database.\n * By default, a volume group is not associated with any volumes at creation\n time. This default is a change from the 4.2 implementation, where newly\n created volume groups contain all of the host volumes. On 5.0 clusters,\n use the `GET /host/{id}/volume` endpoint to query all host volumes.\n\n ## Feature Additions/improvements:\n * Added new endpoint POST/report/data-source/{data_source_name} to get columns\n directly from report data source.\n * Added optional field compliance24HourStatus to RequestFilters object.\n * Added GET /event/event_count-by-status to get job counts based on job status.\n * Added GET /event/event_count-by-job-type to get job counts based on job type.\n * Added GET /event_series endpoint to get all event series information in the\n past 24 hours.\n * Added `oracleDatabase` to ManagedObjectDescendantCounts.\n * Introduced `POST /session/realm/{name}` endpoint to generate session\n tokens in the LDAP display name of {name}.\n * Added optional `storageClass` field to `ObjectStoreReaderConnectDefinition`.\n to store `storageClass` field for the newly connected reader location.\n * Added optional `encryptionType` field to `ObjectStoreLocationSummary` to\n return encryption type used for an object store archival location.\n * Added a new endpoint POST /oracle/db/download/{snapshot_id} to download\n a particular snapshot (and corresponding logs) for Oracle.\n * Added optional `ownerId` and `reference` fields to\n `/managed_volume/{id}/begin_snapshot`.\n * Added new endpoints regarding references to Managed Volumes, which track\n the processes writing to the Managed Volume.\n - GET `/managed_volume/{id}/snapshot/{snapshot_id}/reference/{reference_id}`.\n PUT `/managed_volume/{id}/snapshot/{snapshot_id}/reference/{reference_id}`.\n PATCH\n `/managed_volume/{id}/snapshot/{snapshot_id}/reference/{reference_id}`.\n DELETE\n `/managed_volume/{id}/snapshot/{snapshot_id}/reference/{reference_id}`.\n are the endpoints for viewing, adding, editing and deleting a Managed\n Volume snapshot reference respectively.\n * Added optional `apiToken` and `apiEndpoint` fields to NasConfig to support\n Pure FlashBlade devices.\n * Added optional `smbValidIps`, `smbDomainName` and `smbValidUsers` fields\n to `VolumeGroupMountSnapshotJobConfig` to support secure SMB.\n * Added optional `smbDomainName`, `smbValidIps`, `smbValidUsers` fields to\n ManagedVolumeExportConfig to support secure SMB.\n * Added a new optional field `oracleSysDbaUser` to /host/{id} POST endpoint\n during register host for setting the Oracle username for account with sysdba\n privileges on this host.\n * Added a new endpoint DELETE /smb/domain/{domain_name} to delete the\n SMB Domain.\n * Added a new endpoint POST /smb/domain/{domain_name}/join to configure\n SMB Domain.\n * Added a new optional filed `oracleSysDbaUser` to /host/{id} endpoint for\n changing the Oracle username for account with sysdba privileges on this\n host.\n * Added a new endpoint POST /smb/enable_security to enable Live Mount\n security\n * Made the `numChannels` field in ManagedVolumeConfig optional.\n * Added `applicationTag` field to ManagedVolumeConfig to specify workload\n type for a managed volume.\n * Added Maintenance EventType\n * Added POST `/report/global_object` endpoint to directly query table data\n from GlobalObject based on ReportTableRequest\n * Added new API endpoint GET `/diagnostic/snappable/{id}` returns\n diagnostic information of all backup tasks of a data source.\n * Added new API endpoint GET `/diagnostic/snappable/{id}/latest` returns\n diagnostic information of the most recent backup task of a data source.\n * Added `shareType` field to ManagedVolumeSummary and ManagedVolumeDetail.\n * Added oracle instant recovery API to trigger instant recovery of a\n database.\n * Added RAC, Oracle host and Oracle database fields to the the oracle\n hierarchy API\n * Added a new endpoint GET /smb/domain to get a list of discovered\n SMB domains in the environment.\n * Added a new endpoint GET /notification_setting to get all Notification\n Settings.\n * Added a new endpoint POST /notification_setting to create a new\n Notification Setting.\n * Added a new endpoint GET /notification_setting/{id} to get a Notification\n Setting specified by the input id.\n * Added a new endpoint PATCH /notification_setting/{id} to update the values\n for a specified Notification Setting.\n * Added a new endpoint DELTE /notification_setting/{id} to delete a\n specified Notification Setting.\n * Introduced `POST /oracle/db/snapshot/{id}/export/tablespace` endpoint to\n trigger the export of a single tablespace in an Oracle database.\n * Added a new optional field `shouldRestoreFilesOnly` to POST\n /oracle/db/snapshot/{id}/export endpoint, used when exporting an Oracle\n database, to specify whether the user requires a full recovery of the\n database or a restoration of the database files.\n * Added /oracle/hierarchy/{id}/children endpoint to get children of\n object in Oracle hierarchy\n * Added /oracle/hierarchy/{id}/descendants endpoint to get descendants of\n object in Oracle hierarchy\n * Added a new endpoint POST /fileset/{id}/unprotect, which can be used to\n unprotect a fileset and specify a retention policy to apply to existing\n snapshots.\n * Added a new optional field `existingSnapshotRetention` to POST\n /sla_domain/{id}/assign, used when unprotecting an object, to specify whether\n to retain existing snapshots according to the current SLA domain, keep\n existing snapshots forever, or expire all snapshots immediately. If not\n specified, this field will default to the existing behavior of keeping\n snapshots forever.\n * Introduced `GET /kmip/client` endpoint to get the stored KMIP client\n configuration.\n * Introduced `PUT /kmip/client` endpoint to set the KMIP client configuration.\n * Introduced `GET /kmip/server` endpoint to get stored KMIP server\n information.\n * Introduced `PUT /kmip/server` endpoint to add a a KMIP server.\n * Introduced `DELETE /kmip/server` endpoint to remove a a KMIP server.\n * Introduced `POST /session` endpoint to generate session tokens.\n * Added a new optional field `mfaServerId` to /user endpoint for\n associating a configured MFA server.\n * Added REST support for Oracle RAC, Oracle Host.\n Updated the detail and summary for Oracle Database.\n * Added support to run on-demand backup jobs, export snapshots, live\n mount for Oracle Database.\n * Introduced `POST /mfa/rsa/server` endpoint to\n create a new RSA server configuration for MFA integration.\n * Introduced `GET /mfa/rsa/server` endpoint to\n get a list of RSA server configured for MFA integration.\n * Introduced `PATCH /mfa/rsa/server/{id}` endpoint to\n modify RSA server configuration.\n * Introduced `GET /mfa/rsa/server/{id}` endpoint to\n get RSA server configuration.\n * Introduced `POST /mfa/initialize` to initialize an attempt\n to perform Multifactor authentication for a user.\n * Introduced `POST /mfa/session` to perform Multifactor\n authentication for a user.\n * Introduced `POST /session/api_token` to create an API Token.\n * Added a new optional field `isArrayEnabled` to `FilesetTemplateCreate`.\n for creation of storage array-enabled fileset templates. We also include\n this new field in `FilesetTemplateDetail`.\n * Added a new optional field `arraySpec` to `FilesetCreate` for\n creation of storage array-enabled filesets. We also include\n this new field in `FilesetSummary` and `FilesetDetail`.\n * Introduced `GET /cluster/{id}/is_azure_cloud_only` to query if the cluster\n supports only Azure public cloud.\n * Introduced `POST /unmanaged_object/assign_retention_sla` to set Retention\n SLA of unmanaged objects.\n * Introduced `POST /unmanaged_object/snapshot/assign_sla` to set Retention\n SLA of unmanaged snapshots.\n * Introduced `POST /mssql/db/bulk/snapshot/{id}` to take an on-demand snapshot\n of multiple SQL Server databases. The result of this asynchronous request\n can be obtained from `GET /mssql/db/bulk/snapshot/{id}`.\n * Added a new field unprotectable_reasons to GET /mssql/db/{id} and\n GET /mssql/instance/{id}. This field keeps track of the reasons that a\n SQL Server database or instance cannot be protected by Rubrik.\n * Introduced a new `GET /cluster/me/login_banner` and\n `PUT /cluster/me/login_banner` endpoints to get and set the banner\n that displays after each successful login.\n * Introduced a new `GET /cluster/me/security_classification` and\n `PUT /cluster/me/security_classification` endpoints to get and set\n the security classification banner for the cluster. The cluster UI\n displays the banner in the specified color.\n * Introduced `GET /cluster/{id}/security/rksupport_cred` to provide\n the status of the rksupport credentials.\n * Introduced `POST /cluster/{id}/security/rksupport_cred` to update\n the cluster-wide credentials for the specified cluster.\n * Introduced `POST /vmware/vm/snapshot/{id}/mount_disks` to attach VMDKs\n from a mount snapshot to an existing virtual machine\n * Introduced new `GET /host/{id}/volume` endpoint to query the HostVolume\n from the host.\n * Added the `HostVolumeSummary`, which is the response of the endpoint\n `GET /host/{id}/volume` and a part of `VolumeGroupDetail`.\n * Introduced a new `GET /volume_group/host_layout/{snapshot_id}` and\n `GET /volume_group/{host_id}/host_layout` to get the Windows host layout\n of all disks and volumes.\n * Added `WindowsHostLayout` which is the response of\n `GET /volume_group/host_layout/{snapshot_id}` and\n `GET /volume_group/{host_id}/host_layout`.\n * Added support for Blueprint.\n * Added new fields `retentionSlaDomainId` and `retentionSlaDomainName` to\n UnmanagedObjectSummary object, which is returned from a\n `GET /unmanaged_object` call.\n * Removed `unmanagedSnapshotCount` and added new fields `autoSnapshotCount`.\n and `manualSnapshotCount` to UnmanagedObjectSummary object, which is\n returned from a `GET /unmanaged_object` call.\n * Added new fields `retentionSlaDomainId` and `retentionSlaDomainName` to\n UnmanagedSnapshotSummary object, which is returned from a\n `GET /unmanaged_object/{id}/snapshot` call.\n * Added a new field `hasAttachingDisk` to `GET /vmware/vm/snapshot/mount` and\n `GET /vmware/vm/snapshot/mount/{id}` that indicates to the user whether\n this is an attaching disk mount job.\n * Added a new field `attachingDiskCount` to `GET /vmware/vm/snapshot/mount`.\n and `GET /vmware/vm/snapshot/mount/{id}` that indicate to the user how many\n disks are attached.\n * Added field `RetentionSlaDomainName` to sort_by of a\n `GET * /unmanaged_object/{id}/snapshot` call.\n * Added field `excludedDiskIds` to NutanixVmDetail which is returned from a\n `GET /nutanix/vm/{id}` to exclude certain disks from backup. Also added\n field to NutanixVmPatch via `PATCH /nutanix/vm/{id}` to allow the field\n to be updated.\n * Introduced the `PATCH /aws/ec2_instance/indexing_state` endpoint for\n enabling/disabling indexing per EC2 instance.\n * Added new optional fields `organizationId` and `organizationName` to\n `/host/{id}` and `/host` endpoints to get the organization a host is\n assigned to due to Envoy.\n * Introduced a new `GET /host/envoy` endpoint. Acts similar to queryHost but\n also includes Envoy organization info if Envoy is enabled.\n * Added a new endpoint `GET /vmware/vcenter/{id}/tag_category` to get a list of\n Tag Categories associated with a vCenter.\n * Added a new endpoint `Get /vmware/vcenter/tag_category/{tag_category_id}` to\n get a specific Tag Category associated with a vCenter.\n * Added a new endpoint `GET /vmware/vcenter/{id}/tag` to get a list of Tags\n associated with a vCenter. The optional category_id parameter allow the\n response to be filtered by Tag Category.\n * Added a new endpoint `GET /vmware/vcenter/tag/{tag_id}` to get a\n specific Tag associated with a vCenter.\n * Introduced `GET /cluster/{id}/global_manager_connectivity` to\n retrieve a set of URLs that are pingable from the CDM cluster.\n * Added optional field `instanceName` in `ManagedObjectProperties`.\n * Added new endpoint GET `/cloud_on/aws/app_image/{id}` to retrieve a specified\n AWS AppBlueprint image.\n * Added new endpoint DELETE `/cloud_on/aws/app_image/{id}` to delete the\n given AWS AppBlueprint image.\n * Added new endpoint GET `/cloud_on/azure/app_image/{id}` to retrieve a\n specified Azure AppBlueprint image.\n * Added new endpoint DELETE `/cloud_on/azure/app_image/{id}` to delete the\n given Azure AppBlueprint image.\n * Added organization endpoint for Oracle.\n * Added new endpoint GET `/cloud_on/aws/app_image` to retrieve all\n AWS AppBlueprint images.\n * Added new endpoints `GET /stats/cloud_storage/physical`, `GET\n /stats/cloud_storage/ingested` and `GET /stats/cloud_storage/logical` which\n return respective stats aggregated across all archival locations\n * Added a new endpoint `POST /vmware/standalone_host/datastore` to get a list\n of datastore names for a given ESXi host.\n * Added a new optional field `apiEndpoint` to `NasBaseConfig`.\n\n ### Changes to Internal API in Rubrik version 4.2\n ## Breaking changes:\n * Introduced a new `GET /cluster/{id}/ipv6` endpoint for getting all IPv6\n addresses configured on a specific or all network interfaces.\n * Introduced a new `PATCH /cluster/{id}/ipv6` endpoint for configuring IPv6\n addresses on a specific network interface for each nodes in cluster.\n * Introduced a new `GET /cluster/{id}/trial_edge` for getting whether the\n cluster is a trial edge.\n * Moved the /auth_domain/ endpoint from internal APIs to the v1 APIs.\n * Deprecated `POST /archive/nfs/reconnect` endpoint. Use\n `POST /archive/nfs/reader/connect` instead to connect as a reader to an\n existing NFS archival location.\n * Deprecated `POST /archive/object_store/reconnect` endpoint. Use\n `POST /archive/object_store/reader/connect` instead to connect as a reader to\n an existing object store location.\n * Deprecated `POST /archive/qstar/reconnect` endpoint. Use\n `POST /archive/qstar/reader/connect` instead to connect as a reader to an\n existing QStar archival location.\n * Deprecated `POST /archive/dca/reconnect` endpoint. Use\n `POST /archive/dca/reader/connect` instead to connect as a reader to an\n existing DCA archival location.\n * Removed `POST /hyperv/vm/snapshot/{id}/restore_file` endpoint. Use\n `POST /hyperv/vm/snapshot/{id}/restore_files` instead to support\n multi-files restore for Hyper-V vm.\n * Removed `POST /nutanix/vm/snapshot/{id}/restore_file` endpoint. Use\n `POST /nutainx/vm/snapshot/{id}/restore_files` instead to support\n multi-files restore for Nutanix vm.\n * Removed `search_timezone_offset` parameter from\n `GET /unmanaged_object/{id}/snapshot` endpoint. The endpoint will now\n use configured timezone on the cluster.\n * Renamed the field `id` in `UserDefinition` to `username` for `POST /user`.\n endpoint.\n * Removed the `/mssql/db/sla/{id}/availability_group_conflicts` endpoint.\n * Removed the `/mssql/db/sla/{id}/assign` endpoint.\n * Added support for Envoy VMs for Organization.\n * Modified the `DELETE /storage/array/{id}` endpoint so that it now triggers\n an asynchronous deletion job, responds with an async request object, and\n archives the storage array's hierarchy.\n * Added `numStorageArrayVolumeGroupsArchived` to `DataLocationUsage` which\n is the response of the `GET /stats/data_location/usage` endpoint.\n * Modified `POST /storage/array` endpoint so that it now triggers an\n asynchronous refresh job, and responds with an async request object.\n * Modified the `GET /storage/array/{id}` and `DELETE /storage/array/{id}`.\n endpoints so that the `id` field now corresponds to the managed ID\n instead of the simple ID. The `managed ID` is the ID assigned to the\n storage array object by the Rubrik REST API server.\n * Moved /throttle endpoint to /backup_throttle.\n * Introduced a new `EmailSubscriptionUpdate` object for the request of the\n `PATCH /report/email_subscription/{subscription_id}` endpoint.\n * Introduced a new `ReportSubscriptionOwner` object for the response of\n `GET /report/email_subscription/{subscription_id}` and\n `GET /report/{id}/email_subscription` endpoints.\n * Added the envoyStatus field to the response of the GET /organization\n endpoint.\n * Added new `attachments` field to the `POST /report/{id}/email_subscription`.\n and `PATCH /report/email_subscription/{subscription_id}` endpoints.\n * Removed fields `length` and `isLog` in response of\n `/mssql/db/{id}/restore_files`.\n * Moved the `/cluster/decommissionNode` endpoint to\n `/cluster/decommissionNodes`. The `DecommissionNodeConfig` object is renamed\n as `DecommissionNodesConfig` and now takes in a list of strings which\n correspond to the IDs of the nodes that are to be decommissioned.\n * Moved the `POST /vmware/vm/{id}/register_agent` endpoint from internal\n APIs to the v1 APIs.\n * Added a required field for environment in AzureComputeSummary to support\n Azure Gov Cloud.\n * Remove `POST internal/vmware/vm/snapshot/{id}/mount` endpoint. Use public\n API of `POST v1/vmware/vm/snapshot/{id}/mount`.\n * The input field OperatingSystemType value `Linux` is replaced by `UnixLike`.\n in FilesetTemplateCreateDefinition, used by POST /fileset-template, and\n in FilesetTemplatePatchDefinition, used by PATCH /fileset_template/{id}.\n * The input field operating_system_type value `Linux` is replaced by `UnixLike`.\n in GET /host-fileset and GET /host-count.\n * Added `snmpAgentPort` field to SnmpConfig object.\n\n ## Feature Additions/improvements:\n * Introduced the `GET /node_management/default_gateway` and `POST\n /node_management/default_gateway` endpoint to get and set default gateway.\n * Introduced the `GET cloud_on/aws/instance_type_list` and `GET\n cloud_on/azure/instance_type_list` endpoint to fetch list of instance types\n for aws and azures.\n * Introduced the `GET /aws/account/{id}/subnet` endpoint to fetch an\n information summary for each of the subnets available in an AWS account.\n * Introduced the `GET /aws/account/{id}/security_group` endpoint to fetch an\n information summary for each of the security groups belonging to a particular\n virtual network in an AWS account.\n * Moved definitions `Subnet` and `SecurityGroup` of `definitions/cloud_on.yml`.\n to `definitions/cloud_common.yml` so that both the CloudOn and CloudNative\n features can use them.\n * Introduced the `GET /host/{id}/diagnose` endpoint to support target host\n diagnosis features. Network connectivity (machine/agent ping) implemented\n in the current version.\n * Added vCD endpoints to support vCloud Director. The following endpoints\n have been added to the vcdCluster object:\n - `POST /vcd/cluster` to add a new vCD cluster object.\n * Added support for CRUD operations on vCloud Director cluster objects.\n - POST /vcd/cluster, PATCH /vcd/cluster/{id}, DELETE /vcd/cluster/{id},\n POST /vcd/cluster/{id}/refresh are the endpoints for adding, editing,\n deleting and refreshing a vCD cluster object respectively.\n * Introduced endpoint `GET /search/snapshot_search` to search files in a\n given snapshot. The search supports prefix search only.\n * Introduced the new `POST /storage/array/{id}/refresh` endpoint to\n create a new refresh job to update the Storage Array metadata.\n * Introduced the new `GET /storage/array/request/{id}` endpoint to\n get status of a storage array-related asynchronous request.\n * Introduced the new `POST /storage/array/volume/group` endpoint\n to add a new storage array volume group.\n * Introduced the new `GET /storage/array/volume/group/{id}` endpoint\n to get details of a storage array volume group.\n * Introduced the new `DELETE /storage/array/volume/group/{id}` endpoint\n to remove a storage array volume group.\n * Introduced the new `GET /storage/array/hierarchy/{id}` endpoint\n to get a summary of an object in the storage array hierarchy.\n * Introduced the new `GET /storage/array/hierarchy/{id}/children` endpoint\n to get the children of an object in the storage array hierarchy.\n * Introduced the new `GET /storage/array/hierarchy/{id}/descendants` endpoint\n to get the descendants of an object in the storage array hierarchy.\n * Introduced the new `GET /storage/array/volume` endpoint to get\n summary information of all storage array volumes.\n * Introduced the new `GET /storage/array/volume/{id}` endpoint to get\n details of a storage array volume.\n * Introduced the new `POST /storage/array/volume/group/{id}/snapshot`.\n endpoint to create a new on-demand backup job for a storage array\n volume group.\n * Introduced the new `PATCH /storage/array/volume/group/{id}` endpoint to\n update the properties of a storage array volume group object.\n * Introduced the new `GET /storage/array/volume/group` endpoint to\n get all storage array volume groups subject to specified filters.\n * Introduced endpoint `POST /archive/location/{id}/owner/pause` to pause\n archiving to a given archival location that is owned by the current cluster.\n * Introduced endpoint `POST /archive/location/{id}/owner/resume` to resume\n archiving to a given archival location that is owned by the current cluster.\n * Introduced endpoint `POST /archive/location/{id}/reader/promote` to promote\n the current cluster to be the owner of a specified archival location that is\n currently connected as a reader location.\n * Introduced endpoint `POST /archive/location/{id}/reader/refresh` to sync the\n current reader cluster with the contents on the archival location. This pulls\n in any changes made by the owner cluster to the archival location since the\n last time the current cluster was synced.\n * Introduced endpoint `POST /archive/dca/reader/connect` to connect as a reader\n to a DCA archival location.\n * Introduced endpoint `POST /archive/nfs/reader/connect` to connect as a reader\n to an NFS archival location.\n * Introduced endpoint `POST /archive/object_store/reader/connect` to connect as\n a reader to an object store location.\n * Introduced endpoint `POST /archive/dca/qstar/connect` to connect as a reader\n to a QStar archival location.\n * Updated `ArchivalLocationSummary` returned by `GET /archive/location`.\n endpoint to include `ownershipStatus` field, to indicate whether the current\n cluster is connected to the archival location as an owner (active or paused),\n as a reader, or if the archival location is deleted.\n * Added the `ca_certs` field to `StorageArrayDefinition` to allow admins\n to specify certificates used for validation when making network\n requests to the storage array API service. This effects endpoints\n `POST /storage/array`, `GET /storage/array/{id}`, and\n `PUT /storage/array/{id}`.\n * Introduced the `POST /vmware/vm/snapshot/{id}/download_files` endpoint to\n download multiple files/folders from a given vm snapshot. The URL to\n download the zip file including the files will be presented to the users.\n * Introduced the `POST /fileset/snapshot/{id}/download_files` endpoint to\n download multiple files/folders from a given fileset snapshot. The URL to\n download the zip file including the specific files/folders will be presented\n to the users.\n * Introduced the `POST /nutanix/vm/snapshot/{id}/download_files` endpoint to\n download multiple files/folders from a given nutanix snapshot. The URL to\n download the zip file including the specific files/folders will be presented\n to the users.\n * Removed the `POST /nutanix/vm/snapshot/{id}/download_file` endpoint as\n downloading a single file/folder from the nutanix backup is just a special\n case of downloading multiple files/folders.\n * Introduced the `POST /hyperv/snapshot/{id}/download_files` endpoint to\n download multiple files/folders from a given Hyper-V snapshot. The URL to\n download the zip file including the specific files/folders will be presented\n to the users.\n * Introduced the POST /managed_volume/snapshot/{id}/download_files endpoint\n to download multiple files and/or folders from a given managed volume\n snapshot. This endpoint returns the URL to download the ZIP file that\n contains the specified files and/or folders.\n * Introduced the new `GET /storage/array/volume/group/{id}/search` endpoint to\n search storage array volume group for a file.\n * Introduced the new `GET /storage/array/volume/group/snapshot/{id}`.\n endpoint to retrieve details of a storage array volume group snapshot.\n * Introduced the new `DELETE /storage/array/volume/group/snapshot/{id}`.\n endpoint to remove a storage array volume group snapshot.\n * Introduced the new `DELETE /storage/array/volume/group/{id}` endpoint\n to delete all snapshots of a storage array volume group.\n * Introduced the new `POST /storage/array/volume/group/{id}/download`.\n endpoint to download a storage array volume group snapshot from archival.\n * Introduced new `GET/storage/array/volume/group/snapshot/{id}/restore_files`.\n endpoint to restore files from snapshot of a storage array volume group.\n * Added storage volume endpoints for AWS cloud native workload protection.\n Endpoints added:\n - GET /aws/ec2_instance/{id}/storage_volume/ to retrieve\n all storage volumes details attached to an ec2 instance object.\n - GET /aws/ec2_instance/{ec2_instance_id}/storage_volume/{id} to retrieve\n details of a storage volume attached to an ec2 instance object.\n - POST /aws/ec2_intance/snapshot/{id}/export to export the snapshot of\n an ec2 instance object to a new ec2 instance object.\n * Introduced the new `POST /storage/array/volume/group/{id}/download_file`.\n endpoint to download a file from an archived storage array volume group\n snapshot.\n * Introduced the new `GET /storage/array/volume/group/{id}/missed_snapshot`.\n endpoint to get details about all missed snapshots of a storage array volume\n group.\n * Introduced the `GET /network_throttle` endpoint for retrieving the list of\n network throttles.\n * Introduced the `PATCH /network_throttle/{id}` endpoint for updating\n network throttles.\n * Introduced the new `GET /storage/array/host/{id}` endpoint to get details\n about all storage array volumes connected to a host.\n * Introduced the `GET /organization/{id}/storage/array` endpoint for getting\n information for authorized storage array resources in an organization.\n * Introduced the `GET /organization/{id}/storage/array/volume_group/metric`.\n endpoint for getting storage array volume groups metrics in an\n organization.\n * Introduced the new POST /vmware/vm/snapshot/mount/{id}/rollback endpoint to\n rollback the datastore used by a virtual machine, after an Instant Recovery\n that used the preserve MOID setting. This endpoint `rolls back` the\n recovered virtual machine's datastore from the Rubrik cluster to the\n original datastore.\n * Added `owner` and `status` fields to the `EmailSubscriptionSummary`.\n object used in responses for many `/report/{id}/email_subscription`.\n and `/report/email_subscription/{subscription_id}` endpoints.\n * Added `availableSpace` and `readerLocationSummary` fields to the\n `NfsLocationDetail` object used in responses for `/archive/nfs` and\n `/archive/nfs/{id}` endpoints.\n * Added `availableSpace` and `readerLocationSummary` fields to the\n `QstarLocationSummary` object used in responses for the `/archive/qstar`.\n endpoint.\n * Added `availableSpace` and `readerLocationSummary` fields to the\n `QstarLocationDetail` object used in responses for the `/archive/qstar/{id}`.\n endpoint.\n * Added `readerLocationSummary` field to the `ObjectStoreLocationDetail`.\n object used in responses for the `/archive/object_store` and\n `/archive/object_store/{id}` endpoints.\n * Added `readerLocationSummary` field to the `DcaLocationDetail` object\n used in responses for the `/archive/dca` and `/archive/dca/{id}` endpoints.\n * Added a new field `guestOsType` to `HypervVirtualMachineDetail`.\n object used in response of `GET /hyperv/vm/{id}`.\n * Added a new field `guestOsType` to `VirtualMachineDetail`.\n object referred by `VappVmDetail`.\n * Added new field `fileType` in response of `/mssql/db/{id}/restore_files`.\n * Added an optional field `agentStatus` to `VirtualMachineSummary` object used\n in response of `GET /vmware/vm` endpoint. This allows user to check the\n Rubrik Backup Service connection status of the corresponding VMware VM.\n * Introduced the new `POST /fileset/snapshot/{id}/export_files` endpoint to\n export multiple files or directories to destination host.\n * Introduced the new `GET /vmware/config/esx_subnets` endpoint to get the\n the preferred subnets to reach ESX hosts.\n * Introduced the new `PATCH /vmware/config/reset_esx_subnets` endpoint to\n reset the preferred subnets to reach ESX hosts.\n * Changed the `PATCH /vmware/config/reset_esx_subnets` endpoint to\n `PATCH /vmware/config/set_esx_subnets`.\n * Removed the `needsInspection` field from the NodeStatus object returned in\n the `/cluster/{id}/node` and `/node` endpoints.\n * Introduced the new `PATCH /auth_domain/{id}` endpoint to update the Active\n Directory configuration parameters.\n * Introduced the new `GET /cluster/{id}/auto_removed_node` endpoint to\n query for unacknowledged automatic node removals by the Rubrik cluster.\n * Introduced the new\n `DELETE /cluster/{id}/auto_removed_node/{node_id}/acknowledge` endpoint to\n acknowledge an automatic node removal.\n * Introduced the new `GET /cluster/{id}/system_status` endpoint to retrieve\n information about the status of the Rubrik cluster.\n * Changed the `POST /cloud_on/azure/subscription` endpoint to to take\n the parameter `AzureSubscriptionRequest` instead of\n `AzureSubscriptionCredential` in body.\n * Changed the `POST /cloud_on/azure/storage_account` endpoint to to take\n the parameter `AzureStorageAccountRequest` instead of\n `AzureStorageAccountCredential` in body.\n * Changed the `POST /cloud_on/azure/resource_group` endpoint to take\n the parameter `AzureResourceGroupRequest` instead of\n `AzureResourceGroupCredential` in body.\n * Added a `reportTemplate` field to the response of both the\n `GET /report/{id}/table` and `GET /report/{id}/chart` endpoints.\n\n ### Changes to Internal API in Rubrik version 4.1\n ## Changes to support instance from image\n * POST /aws/instance and /azure/instance was supported only from a Rubrik\n snapshot. Now it is changed to support instantiation from Rubrik snapshot as\n well as pre-existing image. Rest end point is same, we just changed the\n CreateCloudInstanceRequest object type.\n * Add a new field `ignoreErrors` to POST /vmware/vm/snapshot/{id}/restore_files\n that will let job restore ignore file errors during restore job.\n ## Breaking changes:\n * None is removed as a Nutanix snapshot consistency mandate so it is no\n longer valid in GET /nutanix/vm, GET /nutanix/vm/{id}, and\n PATCH /nutanix/vm/{id}.\n * computeSecurityGroupId is replaced by the object defaultComputeNetworkConfig\n in ObjectStoreLocationSummary ,ObjectStoreUpdateDefinition and\n ObjectStoreReconnectDefinition which are used by\n GET /archive/object_store/{id}, PATCH /archive/object_store/{id} and\n POST /archive/object_store/reconnect respectively.\n * The PUT /throttle endpoint was changed to provide configuration for\n Hyper-V adaptive throttling. Three parameters were added:\n hypervHostIoLatencyThreshold, hypervHostCpuUtilizationThreshold, and\n hypervVmCpuUtilizationThreshold. To differentiate between the multiple\n hypervisors, the existing configuration parameters for VMware were renamed\n VmwareVmIoLatencyThreshold, VmwareDatastoreIoLatencyThreshold and\n VmwareCpuUtilizationThreshold. These changes also required modifications\n and additions to the GET /throttle endpoint.\n * For `POST /cluster/{id}/node` endpoint, it gets now `AddNodesConfig` in body\n instead of `Map_NodeConfig` directly.\n * For `POST /node_management/replace_node` endpoint, added the `ipmiPassword`.\n field to the `ReplaceNodeConfig` object.\n * For `POST /stats/system_storage` endpoint, added the miscellaneous, liveMount\n and snapshot field to `SystemStorageStats` object.\n * For `POST /principal_search`, removed `managedId` field from the\n `PrincipalSummary` object and changed the `id` field of the\n `PrincipalSummary` object to correspond to the managed id instead of the\n simple id.\n * For `GET /cluster/{id}/timezone` and `PATCH /cluster/{id}/timezone`, the\n functionality has merged into `GET /cluster/{id}` and `PATCH /cluster/{id}`.\n in v1.\n * Removed the `GET /cluster/{id}/decommissionNodeStatus` endpoint.\n Decommission status is now available through queries of the `jobId` that is\n returned by a decommission request. Queries can be performed at the\n `GET /job/{id}` endpoint.\n * For `GET /api/internal/managed_volume/?name=`, the name match is now\n exact instead of infix\n * Updated the list of available attribute and measure values for the `chart0`.\n and `chart1` parameters for the `PATCH /report/{id}` endpoint.\n * Updated the list of available column values for the `table` parameter for the\n `PATCH /report/{id}` endpoint.\n * Updated the `FolderHierarchy` response object to include\n `effectiveSlaDomainId`, `effectiveSlaDomainName`,\n `effectiveSlaSourceObjectId`, and `effectiveSlaSourceObjectName`.\n\n ## Feature Additions/improvements:\n * Added the field `pendingSnapshotCount` to ManagedVolumeSummary and\n ManagedVolumeDetail objects used in responses for endpoints\n `GET /managed_volume`, `POST /managed_volume`, `GET /managed_volume/{id}`,\n `PATCH /managed_volume/{id}`, `GET /organization/{id}/managed_volume`.\n * Introduced the `GET /managed_volume/snapshot/export/{id}` endpoint\n to retrieve details of a specific managed volume snapshot export.\n * Added the `name` filter for GET requests on the /replication/target endpoint.\n This filter allows users to filter results based on the name of a\n replication target.\n * Added the `name` filter for GET requests on the /archive/location endpoint.\n This filter allows users to filter results based on the name of an\n archival location.\n * Added new fields `replicas` and `availabilityGroupId` on GET /mssql\n and GET /mssql/{id}. If a database is an availability database,\n it will have some number of replicas, which are copies of the database\n running on different instances. Otherwise, there will only be one\n replica, which represents the single copy of the database. The field\n `availabilityGroupId` will be set only for availability databases\n and points to the availability group of the database. Also deprecated\n several fields on these endpoints, as they should now be accessed via\n the `replicas` field.\n * Added `Cluster` notification type.\n * Added optional `organizationId` parameter to to the grant/revoke and get\n authorization endpoints. This parameter can be used to\n grant/revoke/get authorizations with respect to a specific Organization.\n * Added endpoint to get/set whether the Rubrik Backup Service is automatically\n deployed to a guest OS.\n * Added cloudInstantiationSpec field to Hyper-V VM endpoint for configuring\n automatic cloud conversion\n * Introduced a new end point /cluster/{id}/platforminfo to GET information\n about the platform the current software is running on\n * Introduced the `GET /organization` and `GET /organization/{id}` endpoints\n for retrieving the list of organizations and a single organization.\n * Introduced the `POST /organization` endpoint for creating organizations,\n the `PATCH /organization/{id}` endpoint for updating organizations and the\n `DELETE /organization/{id}` endpoint for deleting organizations.\n * Introduced the `GET /organization/{id}/stats/storage_growth_timeseries`.\n endpoint and the `GET /organization/{id}/stats/total_storage_usage` for\n getting Physical Storage Growth over Time and Total Physical Storage Usage\n on a per Organization basis.\n * Introduced a number of endpoints of the format\n `GET /organization/{id}/` for retrieving all the resources of\n the corresponding type in a given organization.\n * Introduced a number of endpoints of the format\n `GET /organization/{id}//metric` for retrieving the protection\n counts of the resources of the corresponding type in a given organization.\n * Added the `reportTemplate` filter for GET requests on the /report endpoint.\n This allows queried reports to be filtered and sorted by report template.\n * Introduced the `POST /cluster/{id}/security/password/strength` endpoint\n for assessing the strength of passwords during bootstrap through rkcli.\n * Added a new `ipv6` field in the response of the `GET /cluster/{id}/discover`.\n endpoint.\n * Added relatedIds field for EventSummary object to give more context about\n the event.\n * Added operatingSystemType field for NutanixSummary object. This field\n represents the type of operating system on the Nutanix virtual machine.\n\n ### Changes to Internal API in Rubrik version 4.0\n ## Breaking changes:\n * For `GET /unmanaged_object` endpoint, replaced the `Fileset` of object_type\n filter with more specific object types: `WindowsFileset`, `LinuxFileset` and\n `ShareFileset`. Also added filter value for additional unmanaged objects\n we now support.\n * For /mssql/db/{id}/compatible_instance added recoveryType as mandatory\n query parameter\n\n ## Feature Additions/improvements:\n * Added QStar end points to support it as an archival location. The location\n is always encrypted and an encryption password must be set while adding the\n location. End points added:\n - `DELETE /archive/qstar` to clean up the data in the bucket in the QStar\n archival location.\n - `GET /archive/qstar` to retrieve a summary of all QStar archival locations.\n - `POST /archive/qstar` to add a QStar archival location.\n - `POST /archive/qstar/reconnect` to reconnect to a specific QStar archival\n location.\n - `POST /archive/qstar/remove_bucket` to remove buckets matching a prefix\n from QStar archival location.\n - `GET /archive/qstar/{id}` to retrieve a summary information from a specific\n QStar archival location.\n - `PATCH /archive/qstar/{id}` to update a specific QStar archival location.\n * Added the `name` filter for GET requests on the /archive/location endpoint.\n This filter allows users to filter results based on the name of an\n archival location.\n * Introduced an optional parameter `encryptionPassword` for the\n `/data_location/nfs` `POST` endpoint. This password is used for\n deriving the master key for encrypting the NFS archival location.\n * Introduced /managed\\_volume, /managed\\_volume/snapshot/export/{id},\n and other child endpoints for creating, deleting, and updating\n Managed Volumes and its exports and snapshots.\n * Added support for Hyper-V.\n * Add new /hierarchy endpoint to support universal hierarchy view.\n * Added support for Nutanix.\n * Moved and merged vCenter refresh status and delete status from independent\n internal endpoints to a single status field in v1 vCenter detail.\n * Added endpoint to get/set whether the Rubrik Backup Service is automatically\n deployed to a guest OS.\n * Introduced an optional parameter `minTolerableNodeFailures` for the\n `/cluster/decommissionNode` `POST` endpoint. This parameter specifies the\n minimum fault tolerance to node failures that must exist when a node is\n decommissioned.\n * Added `nodeId` to `AsyncRequestStatus` to improve debugging job failures.\n\n ### Changes to Internal API in Rubrik version 3.2.0\n ## Breaking changes:\n * Introduced endpoint /host/share/id/search to search for\n files on the network share.\n * Introduced endpoints /host/share and /host/share/id to\n support native network shares under /host endpoint.\n * For /unmanaged_object endpoints, change sort_attr to sort_by\n sort_attr used to accept a comma separated list of column names to sort.\n Now sort_by only accepts a single column name.\n * For /unmanaged_object endpoints, removed the need for object type when\n deleting unmanaged objects and its snapshots.\n\n ## Feature Additions/improvements:\n * Added internal local_ end points. These are used for\n handling operations on per-node auto-scaling config values.\n Please see src/spec/local-config/comments for details.\n * For the response of /mssql/db/{id}/restore_files, added two more fields\n for each file object. They are the original file name and file length\n of the file to be restore.\n * Introduced a new end point /cluster/{id}/is_registered to GET registration\n status. With this change, we can query if the cluster is registered in the\n Rubrik customer database.\n * Introduced a new end point /cluster/{id}/registration_details to POST\n registration details. Customers are expected to get the registration details\n from the support portal. On successful submission of registration details\n with a valid registration id, the cluster will mark itself as registered.\n * For the /mssql/instance/{id} end point, added fields configuredSlaDomainId,\n configuredSlaDomainName, logBackupFrequencyInSeconds, logRetentionHours,\n and copyOnly.\n * Introduced optional parameter keepMacAddresses to\n POST /vmware/vm/snapshot/{id}/mount, /vmware/vm/snapshot/{id}/export, and\n /vmware/vm/snapshot/{id}/instant_recovery endpints.\n This allows new VMs to have the same MAC address as their source VMs.\n\n ## Bug fixes:\n * Made path parameter required in GET /browse. Previously, an error was\n thrown when path was not passed in. This solves that bug.\n",
"x-logo": {
"url": "https://www.rubrik.com/wp-content/uploads/2016/11/Rubrik-Snowflake-small.png"
}
},
"paths": {
"/polaris/replication/source/replicate_app/{snappable_id}": {
"post": {
"parameters": [
{
"name": "snappable_id",
"in": "path",
"description": "Snappable ID of which we are replicating snapshots.",
"required": true,
"type": "string"
},
{
"in": "body",
"name": "definition",
"description": "Polaris source pull replicate definition.",
"required": true,
"schema": {
"$ref": "#/definitions/PolarisPullReplicateDefinition"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
}
},
"definitions": {
"PolarisPullReplicateDefinition": {
"type": "object",
"required": [
"accessKey",
"isOnDemand",
"polarisId",
"secretKey",
"snapshotInfo"
],
"properties": {
"polarisId": {
"type": "string",
"description": "Managed ID of the Polaris source cluster."
},
"snapshotInfo": {
"description": "Info of the snapshot which this cluster is replicating from Polaris.",
"$ref": "#/definitions/ReplicationSnapshotInfo"
},
"accessKey": {
"type": "string",
"description": "The access key used for accessing customer's volumes to pull replicate snapshots."
},
"secretKey": {
"type": "string",
"description": "The secret key used for accessing customer's volumes to pull replicate snapshots.",
"x-secret": true
},
"isOnDemand": {
"type": "boolean",
"description": "Indicates if snapshot is on-demand."
}
}
},
"ReplicationSnapshotInfo": {
"type": "object",
"required": ["snappableId", "snapshotId"],
"properties": {
"snappableId": {
"type": "string",
"description": "The ID of the snappable stored on this cluster."
},
"snapshotId": {
"type": "string",
"description": "The ID of the snapshot that is being replicated."
},
"snapshotDate": {
"type": "integer",
"format": "int64",
"description": "The date when the snapshot was taken in number of milliseconds since the UNIX epoch. This is a required field when the replication source is Polaris."
},
"snapshotDiskInfos": {
"type": "array",
"description": "An array of the details of the snapshot disks that need to be replicated. This is a required field when the replication source is Polaris.",
"items": {
"$ref": "#/definitions/ReplicationSnapshotDiskInfo"
}
},
"appMetadata": {
"type": "string",
"description": "Serialized metadata specific to the snappable which is being replicated. This is a required field when the replication source is Polaris."
},
"childSnapshotInfos": {
"type": "array",
"description": "An array of child snapshots information.",
"items": {
"$ref": "#/definitions/ReplicationSnapshotInfo"
}
}
}
},
"ReplicationSnapshotDiskInfo": {
"type": "object",
"required": [
"diskFailoverInfo",
"diskId",
"isOsDisk",
"logicalSizeInBytes",
"snapshotDiskId"
],
"properties": {
"diskId": {
"type": "string",
"description": "The ID of the disk/volume that is being replicated."
},
"snapshotDiskId": {
"type": "string",
"description": "The ID of the snapshot of the disk/volume taken on the source that needs to be replicated."
},
"logicalSizeInBytes": {
"type": "integer",
"format": "int64",
"description": "Size of the disk/volume that is being replicated."
},
"isOsDisk": {
"type": "boolean",
"description": "Flag to specify if the disk is OS disk."
},
"diskFailoverInfo": {
"description": "Details specific to the target snappable required to failover the EBS volumes.",
"$ref": "#/definitions/InstanceFailoverInfo"
}
}
},
"InstanceFailoverInfo": {
"type": "object",
"required": ["originalDiskIdentifier"],
"properties": {
"originalDiskIdentifier": {
"type": "string",
"description": "The identifier used to map the original disks before failover to the disks being replicated. For vmware to AWS, this would be the deviceKey of the vmware virtual disk this EBS volume corresponds to."
}
}
}
}
}
kin-openapi-0.124.0/openapi3/testdata/issue638/ 0000775 0000000 0000000 00000000000 14604223742 0021014 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/issue638/test1.yaml 0000664 0000000 0000000 00000000525 14604223742 0022742 0 ustar 00root root 0000000 0000000 openapi: "3.0.0"
info:
version: 1.0.0
title: reference test part 1
description: reference test part 1
components:
schemas:
test1a:
$ref: "test2.yaml#/components/schemas/test2a"
test1b:
$ref: "#/components/schemas/test1c"
test1c:
type: int
test1d:
$ref: "test2.yaml#/components/schemas/test2b"
kin-openapi-0.124.0/openapi3/testdata/issue638/test2.yaml 0000664 0000000 0000000 00000000401 14604223742 0022734 0 ustar 00root root 0000000 0000000 openapi: "3.0.0"
info:
version: 1.0.0
title: reference test part 2
description: reference test part 2
components:
schemas:
test2a:
type: number
test2b:
$ref: "test1.yaml#/components/schemas/test1b"
test1c:
type: string
kin-openapi-0.124.0/openapi3/testdata/issue652/ 0000775 0000000 0000000 00000000000 14604223742 0021010 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/issue652/definitions.yml 0000664 0000000 0000000 00000000106 14604223742 0024043 0 ustar 00root root 0000000 0000000 components:
schemas:
TestSchema:
type: string
kin-openapi-0.124.0/openapi3/testdata/issue652/nested/ 0000775 0000000 0000000 00000000000 14604223742 0022272 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/issue652/nested/schema.yml 0000664 0000000 0000000 00000000203 14604223742 0024250 0 ustar 00root root 0000000 0000000 components:
schemas:
ReferenceToParentDirectory:
$ref: "../definitions.yml#/components/schemas/TestSchema"
kin-openapi-0.124.0/openapi3/testdata/issue697.yml 0000664 0000000 0000000 00000000336 14604223742 0021546 0 ustar 00root root 0000000 0000000 openapi: 3.0.1
components:
schemas:
API:
properties:
dateExample:
type: string
format: date
example: 2019-09-12
info:
title: sample
version: version not set
paths: {}
kin-openapi-0.124.0/openapi3/testdata/issue753.yml 0000664 0000000 0000000 00000002257 14604223742 0021543 0 ustar 00root root 0000000 0000000 openapi: '3'
info:
version: 0.0.1
title: 'test'
paths:
/test1:
post:
requestBody:
content:
application/json:
schema:
type: object
responses:
'200':
description: 'test'
callbacks:
callback1:
'{$request.body#/callback}':
post:
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/test'
responses:
'200':
description: 'test'
/test2:
post:
requestBody:
content:
application/json:
schema:
type: object
responses:
'200':
description: 'test'
callbacks:
callback2:
'{$request.body#/callback}':
post:
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/test'
responses:
'200':
description: 'test'
components:
schemas:
test:
type: string
kin-openapi-0.124.0/openapi3/testdata/issue794.yml 0000664 0000000 0000000 00000000325 14604223742 0021542 0 ustar 00root root 0000000 0000000 openapi: 3.0.1
info:
title: Swagger API
version: v1
paths:
/test:
post:
requestBody:
content:
application/json:
responses:
"200":
description: description
kin-openapi-0.124.0/openapi3/testdata/issue796.yml 0000664 0000000 0000000 00001032542 14604223742 0021553 0 ustar 00root root 0000000 0000000 openapi: 3.0.1
servers:
- url: https://api.presalytics.io/ooxml-automation
info:
description: This API helps users convert Excel and Powerpoint documents into rich, live dashboards and stories.
title: OOXML Automation
version: 0.1.0
x-apisguru-categories:
- analytics
x-logo:
url: https://presalytics.io/static/img/Logo/Logos/Orange-White/navbar.png
x-origin:
- format: openapi
url: https://api.presalytics.io/ooxml-automation/docs/v1/openapi.json
version: "3.0"
x-providerName: presalytics.io
x-serviceName: ooxml
tags:
- description: Charts
name: Charts
- description: Charts / Axes
name: Charts/Axes
- description: Charts / AxisDataTypes
name: Charts/AxisDataTypes
- description: Charts / ChartData
name: Charts/ChartData
- description: Charts / ColumnCollections
name: Charts/ColumnCollections
- description: Charts / Columns
name: Charts/Columns
- description: Charts / DataPoints
name: Charts/DataPoints
- description: Charts / PlotType
name: Charts/PlotType
- description: Charts / RowCol
name: Charts/RowCol
- description: Charts / RowCollections
name: Charts/RowCollections
- description: Charts / RowNameFormatTypes
name: Charts/RowNameFormatTypes
- description: Charts / Rows
name: Charts/Rows
- description: ConnectionShapes
name: ConnectionShapes
- description: Documents
name: Documents
- description: Documents / DocumentType
name: Documents/DocumentType
- description: Groups
name: Groups
- description: Images
name: Images
- description: Shapes
name: Shapes
- description: ShapeTrees
name: ShapeTrees
- description: Shared / ColorTransformationAttributes
name: Shared/ColorTransformationAttributes
- description: Shared / ColorTransformations
name: Shared/ColorTransformations
- description: Shared / ColorTypes
name: Shared/ColorTypes
- description: Shared / DashTypes
name: Shared/DashTypes
- description: Shared / EffectAttributes
name: Shared/EffectAttributes
- description: Shared / Effects
name: Shared/Effects
- description: Shared / EffectTypes
name: Shared/EffectTypes
- description: Shared / FillMap
name: Shared/FillMap
- description: Shared / FillTypes
name: Shared/FillTypes
- description: Shared / GradientFills
name: Shared/GradientFills
- description: Shared / GradientStops
name: Shared/GradientStops
- description: Shared / ImageFills
name: Shared/ImageFills
- description: Shared / LineEndSizes
name: Shared/LineEndSizes
- description: Shared / LineEndTypes
name: Shared/LineEndTypes
- description: Shared / Lines
name: Shared/Lines
- description: Shared / Paragraph
name: Shared/Paragraph
- description: Shared / SolidFills
name: Shared/SolidFills
- description: Shared / Text
name: Shared/Text
- description: Shared / TextContainer
name: Shared/TextContainer
- description: Slides
name: Slides
- description: Slides / ColorMaps
name: Slides/ColorMaps
- description: Slides / Graphics
name: Slides/Graphics
- description: Slides / GraphicTypes
name: Slides/GraphicTypes
- description: Slides / GroupElements
name: Slides/GroupElements
- description: Slides / GroupElementTypes
name: Slides/GroupElementTypes
- description: Slides / SlideMasters
name: Slides/SlideMasters
- description: SmartArts
name: SmartArts
- description: Tables
name: Tables
- description: Tables / Borders
name: Tables/Borders
- description: Tables / Cells
name: Tables/Cells
- description: Tables / Columns
name: Tables/Columns
- description: Tables / Rows
name: Tables/Rows
- description: Themes
name: Themes
- description: Themes / BackgroundFills
name: Themes/BackgroundFills
- description: Themes / Colors
name: Themes/Colors
- description: Themes / CustomColors
name: Themes/CustomColors
- description: Themes / EffectMap
name: Themes/EffectMap
- description: Themes / Fills
name: Themes/Fills
- description: Themes / Fonts
name: Themes/Fonts
- description: Themes / Intensity
name: Themes/Intensity
- description: Themes / LineMap
name: Themes/LineMap
paths:
"/Charts/Axes/{id}":
get:
description: "Get by Id: Use this method to retrieve a Axes object by its primary key (id)"
operationId: chart_axes_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Chart.Axes"
description: Success
"401":
description: Unauthorized
summary: "Axes: Get by Id"
tags:
- Charts/Axes
/Charts/AxisDataTypes:
get:
description: "List Types: Use this method to retreive a list of possible options for the AxisDataTypes type. Use the Id from oneof the returned elements on to make changes to elements in the Chart object space."
operationId: chart_axisdatatypes_get
responses:
"200":
content:
application/json:
schema:
items:
$ref: "#/components/schemas/Chart.AxisDataTypes"
type: array
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "AxisDataTypes: List All Possible Types"
tags:
- Charts/AxisDataTypes
x-operationName: list
"/Charts/AxisDataTypes/TypeId/{type_id}":
get:
description: This endpoint returns Type metadata from an integer type_id that can found on objects throughout the api.
operationId: chart_axisdatatypes_typeid_get_type_id
parameters:
- in: path
name: type_id
required: true
schema:
format: int32
type: integer
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Chart.AxisDataTypes"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "AxisDataTypes: Get By Type Id"
tags:
- Charts/AxisDataTypes
x-operationName: type-id
"/Charts/AxisDataTypes/{id}":
get:
description: "Get by Id: Use this method to retrieve a AxisDataTypes object by its primary key (id)"
operationId: chart_axisdatatypes_get_id
parameters:
- description: ""
in: path
name: id
required: true
schema:
description: ""
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Chart.AxisDataTypes"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "AxisDataTypes: Get by Id"
tags:
- Charts/AxisDataTypes
"/Charts/ChartData/{id}":
get:
description: "Get by Id: Use this method to retrieve a ChartData object by its primary key (id)"
operationId: chart_chartdata_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Chart.ChartData"
description: Success
"401":
description: Unauthorized
summary: "ChartData: Get by Id"
tags:
- Charts/ChartData
"/Charts/ChartUpdate/{id}":
get:
description: Gets a ChartDataDTO object, usually used for downstream analytics and chart updates
operationId: charts_charts_chartupdate_get_id
parameters:
- description: ""
in: path
name: id
required: true
schema:
description: ""
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Chart.ChartDataDTO"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "Charts: Get Chart Data"
tags:
- Charts
put:
description: Simplifies chart update by allowing users to supply data via ChartDataDTO
operationId: charts_charts_chartupdate_put_id
parameters:
- description: The Chart Id
in: path
name: id
required: true
schema:
description: The Chart Id
format: uuid
type: string
requestBody:
content:
application/*+json:
schema:
$ref: "#/components/schemas/Chart.ChartDataDTO"
application/json:
schema:
$ref: "#/components/schemas/Chart.ChartDataDTO"
application/json-patch+json:
schema:
$ref: "#/components/schemas/Chart.ChartDataDTO"
text/json:
schema:
$ref: "#/components/schemas/Chart.ChartDataDTO"
description: The ChartDataDto Object
responses:
"200":
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
summary: "Charts: Update Chart Data"
tags:
- Charts
"/Charts/ChildObjects/{id}":
get:
description: This endpoint is helpful for helping users and bots identify dependent objects to this Chart and retreive their corresponding metadata in order to make modifications to those objects in downstream operations.
operationId: charts_charts_childobjects_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
items:
$ref: "#/components/schemas/ChildObjects"
type: array
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Forbidden
summary: "Charts: Get Dependent Objects Tree"
tags:
- Charts
x-operationName: get-obj-tree
"/Charts/ColumnCollections/{id}":
get:
description: "Get by Id: Use this method to retrieve a ColumnCollections object by its primary key (id)"
operationId: chart_columncollections_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Chart.ColumnCollections"
description: Success
"401":
description: Unauthorized
summary: "ColumnCollections: Get by Id"
tags:
- Charts/ColumnCollections
"/Charts/Columns/{id}":
get:
description: "Get by Id: Use this method to retrieve a Columns object by its primary key (id)"
operationId: chart_columns_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Chart.Columns"
description: Success
"401":
description: Unauthorized
summary: "Columns: Get by Id"
tags:
- Charts/Columns
"/Charts/DataPoints/{id}":
get:
description: "Get by Id: Use this method to retrieve a DataPoints object by its primary key (id)"
operationId: chart_datapoints_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Chart.DataPoints"
description: Success
"401":
description: Unauthorized
summary: "DataPoints: Get by Id"
tags:
- Charts/DataPoints
"/Charts/Details/{id}":
get:
description: "Returns object metadata and information about relative dependent objects "
operationId: charts_charts_details_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Chart.Charts.Details"
description: Success
"401":
description: Unauthorized
summary: "Charts: Get Details"
tags:
- Charts
x-operationName: details
"/Charts/OpenOfficeXml/{id}":
get:
description: Return the subset of the xml content from within the latest edited version of the OpenXmlDocument from this Chart object. The returned xml data conforms to the [Ecma-376 standard](http://www.ecma-international.org/publications/standards/Ecma-376.htm). Use this endpoint a starting point for building client-side extensions that modify Presalytics widgets containing Chart objects.
operationId: charts_charts_openofficexml_get_id_updated
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
- description: Indicates whether API should return the orginal uploaded xml (false) or the actively updated version (true, default)
in: query
name: updated
schema:
default: true
type: boolean
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
summary: "Charts: Get Underlying Xml"
tags:
- Charts
x-operationName: get-xml
put:
description: Directly eidt the underlying xml of a Chart object within an OpenOpenXml (e.g. Excel, Powerpoint) document. This endpoint will validatate the submitted xml against the [Ecma-376 standard](http://www.ecma-international.org/publications/standards/Ecma-376.htm) prior to saving modification. Invalid xml will rejected by this endpoint and a (hopefully) helpful error message will be returned. Use this endpoint for client-side automation of modifications ot Chart objects.
operationId: charts_charts_openofficexml_put_id
parameters:
- in: path
name: id
required: true
schema:
type: string
requestBody:
content:
application/*+json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
application/json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
application/json-patch+json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
text/json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
responses:
"200":
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
summary: "Charts: Modify Underlying Xml"
tags:
- Charts
x-operationName: put-xml
/Charts/PlotType:
get:
description: "List Types: Use this method to retreive a list of possible options for the PlotType type. Use the Id from oneof the returned elements on to make changes to elements in the Chart object space."
operationId: chart_plottype_get
responses:
"200":
content:
application/json:
schema:
items:
$ref: "#/components/schemas/Chart.PlotType"
type: array
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "PlotType: List All Possible Types"
tags:
- Charts/PlotType
x-operationName: list
"/Charts/PlotType/TypeId/{type_id}":
get:
description: This endpoint returns Type metadata from an integer type_id that can found on objects throughout the api.
operationId: chart_plottype_typeid_get_type_id
parameters:
- in: path
name: type_id
required: true
schema:
format: int32
type: integer
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Chart.PlotType"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "PlotType: Get By Type Id"
tags:
- Charts/PlotType
x-operationName: type-id
"/Charts/PlotType/{id}":
get:
description: "Get by Id: Use this method to retrieve a PlotType object by its primary key (id)"
operationId: chart_plottype_get_id
parameters:
- description: ""
in: path
name: id
required: true
schema:
description: ""
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Chart.PlotType"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "PlotType: Get by Id"
tags:
- Charts/PlotType
/Charts/RowCol:
get:
description: "List Types: Use this method to retreive a list of possible options for the RowCol type. Use the Id from oneof the returned elements on to make changes to elements in the Chart object space."
operationId: chart_rowcol_get
responses:
"200":
content:
application/json:
schema:
items:
$ref: "#/components/schemas/Chart.RowCol"
type: array
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "RowCol: List All Possible Types"
tags:
- Charts/RowCol
x-operationName: list
"/Charts/RowCol/TypeId/{type_id}":
get:
description: This endpoint returns Type metadata from an integer type_id that can found on objects throughout the api.
operationId: chart_rowcol_typeid_get_type_id
parameters:
- in: path
name: type_id
required: true
schema:
format: int32
type: integer
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Chart.RowCol"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "RowCol: Get By Type Id"
tags:
- Charts/RowCol
x-operationName: type-id
"/Charts/RowCol/{id}":
get:
description: "Get by Id: Use this method to retrieve a RowCol object by its primary key (id)"
operationId: chart_rowcol_get_id
parameters:
- description: ""
in: path
name: id
required: true
schema:
description: ""
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Chart.RowCol"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "RowCol: Get by Id"
tags:
- Charts/RowCol
"/Charts/RowCollections/{id}":
get:
description: "Get by Id: Use this method to retrieve a RowCollections object by its primary key (id)"
operationId: chart_rowcollections_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Chart.RowCollections"
description: Success
"401":
description: Unauthorized
summary: "RowCollections: Get by Id"
tags:
- Charts/RowCollections
/Charts/RowNameFormatTypes:
get:
description: "List Types: Use this method to retreive a list of possible options for the RowNameFormatTypes type. Use the Id from oneof the returned elements on to make changes to elements in the Chart object space."
operationId: chart_rownameformattypes_get
responses:
"200":
content:
application/json:
schema:
items:
$ref: "#/components/schemas/Chart.RowNameFormatTypes"
type: array
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "RowNameFormatTypes: List All Possible Types"
tags:
- Charts/RowNameFormatTypes
x-operationName: list
"/Charts/RowNameFormatTypes/TypeId/{type_id}":
get:
description: This endpoint returns Type metadata from an integer type_id that can found on objects throughout the api.
operationId: chart_rownameformattypes_typeid_get_type_id
parameters:
- in: path
name: type_id
required: true
schema:
format: int32
type: integer
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Chart.RowNameFormatTypes"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "RowNameFormatTypes: Get By Type Id"
tags:
- Charts/RowNameFormatTypes
x-operationName: type-id
"/Charts/RowNameFormatTypes/{id}":
get:
description: "Get by Id: Use this method to retrieve a RowNameFormatTypes object by its primary key (id)"
operationId: chart_rownameformattypes_get_id
parameters:
- description: ""
in: path
name: id
required: true
schema:
description: ""
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Chart.RowNameFormatTypes"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "RowNameFormatTypes: Get by Id"
tags:
- Charts/RowNameFormatTypes
"/Charts/Rows/{id}":
get:
description: "Get by Id: Use this method to retrieve a Rows object by its primary key (id)"
operationId: chart_rows_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Chart.Rows"
description: Success
"401":
description: Unauthorized
summary: "Rows: Get by Id"
tags:
- Charts/Rows
"/Charts/Svg/{id}":
get:
description: This endpoint is helpful for rending this Chart as an svg or image object that can then be rendered in a story, dashboard or website.
operationId: charts_charts_svg_get_id_use_cache
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
- description: Indicates whether API should retrieve content from a cache if aviable (true, default), or force an update (false)
in: query
name: use_cache
schema:
default: false
type: boolean
responses:
"200":
content:
image/svg+xml:
schema:
description: The file byte stream.
format: binary
title: Svg byte stream
type: string
description: Returns an svg formatted-image of this object.
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
summary: "Charts: Get Svg file"
tags:
- Charts
x-operationName: get-svg
"/Charts/{id}":
get:
description: "Get by Id: Use this method to retrieve a Charts object by its primary key (id)"
operationId: charts_charts_get_id
parameters:
- description: An Id of the respository DTO elemennt
in: path
name: id
required: true
schema:
description: An Id of the respository DTO elemennt
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Chart.Charts"
description: Success
"401":
description: Unauthorized
summary: "Charts: Get by Id"
tags:
- Charts
"/ConnectionShapes/ChildObjects/{id}":
get:
description: This endpoint is helpful for helping users and bots identify dependent objects to this Slide and retreive their corresponding metadata in order to make modifications to those objects in downstream operations.
operationId: slides_connectionshapes_childobjects_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
items:
$ref: "#/components/schemas/ChildObjects"
type: array
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Forbidden
summary: "Slides: Get Dependent Objects Tree"
tags:
- ConnectionShapes
x-operationName: get-obj-tree
"/ConnectionShapes/Details/{id}":
get:
description: "Returns object metadata and information about relative dependent objects "
operationId: slides_connectionshapes_details_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Slide.Connector.Details"
description: Success
"401":
description: Unauthorized
summary: "Slides: Get Details"
tags:
- ConnectionShapes
x-operationName: details
"/ConnectionShapes/OpenOfficeXml/{id}":
get:
description: Return the subset of the xml content from within the latest edited version of the OpenXmlDocument from this Slide object. The returned xml data conforms to the [Ecma-376 standard](http://www.ecma-international.org/publications/standards/Ecma-376.htm). Use this endpoint a starting point for building client-side extensions that modify Presalytics widgets containing Slide objects.
operationId: slides_connectionshapes_openofficexml_get_id_updated
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
- description: Indicates whether API should return the orginal uploaded xml (false) or the actively updated version (true, default)
in: query
name: updated
schema:
default: true
type: boolean
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
summary: "Slides: Get Underlying Xml"
tags:
- ConnectionShapes
x-operationName: get-xml
put:
description: Directly eidt the underlying xml of a Slide object within an OpenOpenXml (e.g. Excel, Powerpoint) document. This endpoint will validatate the submitted xml against the [Ecma-376 standard](http://www.ecma-international.org/publications/standards/Ecma-376.htm) prior to saving modification. Invalid xml will rejected by this endpoint and a (hopefully) helpful error message will be returned. Use this endpoint for client-side automation of modifications ot Slide objects.
operationId: slides_connectionshapes_openofficexml_put_id
parameters:
- in: path
name: id
required: true
schema:
type: string
requestBody:
content:
application/*+json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
application/json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
application/json-patch+json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
text/json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
responses:
"200":
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
summary: "Slides: Modify Underlying Xml"
tags:
- ConnectionShapes
x-operationName: put-xml
"/ConnectionShapes/Svg/{id}":
get:
description: This endpoint is helpful for rending this Slide as an svg or image object that can then be rendered in a story, dashboard or website.
operationId: slides_connectionshapes_svg_get_id_use_cache
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
- description: Indicates whether API should retrieve content from a cache if aviable (true, default), or force an update (false)
in: query
name: use_cache
schema:
default: false
type: boolean
responses:
"200":
content:
image/svg+xml:
schema:
description: The file byte stream.
format: binary
title: Svg byte stream
type: string
description: Returns an svg formatted-image of this object.
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
summary: "Slides: Get Svg file"
tags:
- ConnectionShapes
x-operationName: get-svg
"/ConnectionShapes/{id}":
get:
description: "Get by Id: Use this method to retrieve a ConnectionShapes object by its primary key (id)"
operationId: slides_connectionshapes_get_id
parameters:
- description: An Id of the respository DTO elemennt
in: path
name: id
required: true
schema:
description: An Id of the respository DTO elemennt
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Slide.Connector"
description: Success
"401":
description: Unauthorized
summary: "ConnectionShapes: Get by Id"
tags:
- ConnectionShapes
/Documents:
post:
description: Upload an OpenOfficeXml document (e.g., .xlsx, .pptx) for processing by the API.
operationId: documents_post
requestBody:
content:
multipart/form-data:
schema:
properties:
file:
description: The file to upload. Must be of type .pptx, ppt
format: binary
title: Story File
type: string
storyId:
description: The story_id of the document being uploaded.
format: uuid
title: Story Id
type: string
required:
- file
- storyId
title: Story file form data
type: object
responses:
"200":
content:
application/json:
schema:
items:
$ref: "#/components/schemas/Document"
type: array
text/json:
schema:
items:
$ref: "#/components/schemas/Document"
type: array
text/plain:
schema:
items:
$ref: "#/components/schemas/Document"
type: array
description: Success
"400":
description: Bad Request
summary: "Documents: Upload"
tags:
- Documents
"/Documents/ChildObjects/{id}":
get:
description: This endpoint is helpful for helping users and bots identify dependent objects to this DocumentsController and retreive their corresponding metadata in order to make modifications to those objects in downstream operations.
operationId: documents_childobjects_get_id
parameters:
- description: ""
in: path
name: id
required: true
schema:
description: ""
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
items:
$ref: "#/components/schemas/ChildObjects"
type: array
text/json:
schema:
items:
$ref: "#/components/schemas/ChildObjects"
type: array
text/plain:
schema:
items:
$ref: "#/components/schemas/ChildObjects"
type: array
description: Success
"400":
description: Bad Request
"403":
description: Forbidden
summary: "DocumentsController: Get Dependent Objects Tree"
tags:
- Documents
x-operationName: get-obj-tree
"/Documents/Clone/{id}":
post:
description: Clone A Document that has already been uploaded to a new Story
operationId: documents_clone_post_id
parameters:
- description: the Source document Id
in: path
name: id
required: true
schema:
description: the Source document Id
format: uuid
type: string
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/DocumentCloneDTO"
description: A DocumentCloneDto object with containing information required for cloning the document
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Document"
text/json:
schema:
$ref: "#/components/schemas/Document"
text/plain:
schema:
$ref: "#/components/schemas/Document"
description: Success
"400":
description: Bad Request
summary: "Documents: Clone an existing Ooxml Document to new Parent Story"
tags:
- Documents
/Documents/DocumentType:
get:
description: "List Types: Use this method to retreive a list of possible options for the DocumentType type. Use the Id from oneof the returned elements on to make changes to elements in the Documents object space."
operationId: documents_documenttype_get
responses:
"200":
content:
application/json:
schema:
items:
$ref: "#/components/schemas/DocumentType"
type: array
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "DocumentType: List All Possible Types"
tags:
- Documents/DocumentType
x-operationName: list
"/Documents/DocumentType/TypeId/{type_id}":
get:
description: This endpoint returns Type metadata from an integer type_id that can found on objects throughout the api.
operationId: documents_documenttype_typeid_get_type_id
parameters:
- in: path
name: type_id
required: true
schema:
format: int32
type: integer
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/DocumentType"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "DocumentType: Get By Type Id"
tags:
- Documents/DocumentType
x-operationName: type-id
"/Documents/DocumentType/{id}":
get:
description: "Get by Id: Use this method to retrieve a DocumentType object by its primary key (id)"
operationId: documents_documenttype_get_id
parameters:
- description: ""
in: path
name: id
required: true
schema:
description: ""
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/DocumentType"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "DocumentType: Get by Id"
tags:
- Documents/DocumentType
"/Documents/Download/{id}":
get:
description: Download the into a bytestream for client-side processing.
operationId: documents_download_get_id_orginal
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
- in: query
name: orginal
schema:
default: false
type: boolean
responses:
"200":
content:
application/octet-stream:
schema:
format: binary
title: File byte stream
type: string
description: Returns an updated version of the OpenOffice Xml file
"400":
description: Bad Request
"401":
description: Unauthorized
summary: "Documents: Download"
tags:
- Documents
x-operationName: download
"/Documents/{id}":
delete:
description: "Permantly delete a document from the Ooxml Automation API. Note that is does not make changes to the related Presalytics APIs.\r
Please use the delete endpoint in the story API to ensure that stories are not left with orphaned references to the Ooxml Automation API."
operationId: documents_delete_id
parameters:
- description: ""
in: path
name: id
required: true
schema:
description: ""
format: uuid
type: string
responses:
"204":
description: Success
"400":
description: Bad Request
"403":
description: Forbidden
summary: "Documents: Delete by Id"
tags:
- Documents
get:
description: "Get by Id: Use this method to retrieve a Documents object by its primary key (id)"
operationId: documents_get_id
parameters:
- description: ""
in: path
name: id
required: true
schema:
description: ""
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Document"
text/json:
schema:
$ref: "#/components/schemas/Document"
text/plain:
schema:
$ref: "#/components/schemas/Document"
description: Success
"400":
description: Bad Request
"401":
description: Unauthorized
"404":
description: Not Found
summary: "Documents: Get by Id"
tags:
- Documents
"/Groups/ChildObjects/{id}":
get:
description: This endpoint is helpful for helping users and bots identify dependent objects to this Slide and retreive their corresponding metadata in order to make modifications to those objects in downstream operations.
operationId: slides_groups_childobjects_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
items:
$ref: "#/components/schemas/ChildObjects"
type: array
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Forbidden
summary: "Slides: Get Dependent Objects Tree"
tags:
- Groups
x-operationName: get-obj-tree
"/Groups/Details/{id}":
get:
description: "Returns object metadata and information about relative dependent objects "
operationId: slides_groups_details_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Slide.Groups.Details"
description: Success
"401":
description: Unauthorized
summary: "Slides: Get Details"
tags:
- Groups
x-operationName: details
"/Groups/OpenOfficeXml/{id}":
get:
description: Return the subset of the xml content from within the latest edited version of the OpenXmlDocument from this Slide object. The returned xml data conforms to the [Ecma-376 standard](http://www.ecma-international.org/publications/standards/Ecma-376.htm). Use this endpoint a starting point for building client-side extensions that modify Presalytics widgets containing Slide objects.
operationId: slides_groups_openofficexml_get_id_updated
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
- description: Indicates whether API should return the orginal uploaded xml (false) or the actively updated version (true, default)
in: query
name: updated
schema:
default: true
type: boolean
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
summary: "Slides: Get Underlying Xml"
tags:
- Groups
x-operationName: get-xml
put:
description: Directly eidt the underlying xml of a Slide object within an OpenOpenXml (e.g. Excel, Powerpoint) document. This endpoint will validatate the submitted xml against the [Ecma-376 standard](http://www.ecma-international.org/publications/standards/Ecma-376.htm) prior to saving modification. Invalid xml will rejected by this endpoint and a (hopefully) helpful error message will be returned. Use this endpoint for client-side automation of modifications ot Slide objects.
operationId: slides_groups_openofficexml_put_id
parameters:
- in: path
name: id
required: true
schema:
type: string
requestBody:
content:
application/*+json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
application/json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
application/json-patch+json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
text/json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
responses:
"200":
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
summary: "Slides: Modify Underlying Xml"
tags:
- Groups
x-operationName: put-xml
"/Groups/Svg/{id}":
get:
description: This endpoint is helpful for rending this Slide as an svg or image object that can then be rendered in a story, dashboard or website.
operationId: slides_groups_svg_get_id_use_cache
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
- description: Indicates whether API should retrieve content from a cache if aviable (true, default), or force an update (false)
in: query
name: use_cache
schema:
default: false
type: boolean
responses:
"200":
content:
image/svg+xml:
schema:
description: The file byte stream.
format: binary
title: Svg byte stream
type: string
description: Returns an svg formatted-image of this object.
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
summary: "Slides: Get Svg file"
tags:
- Groups
x-operationName: get-svg
"/Groups/{id}":
get:
description: "Get by Id: Use this method to retrieve a Groups object by its primary key (id)"
operationId: slides_groups_get_id
parameters:
- description: An Id of the respository DTO elemennt
in: path
name: id
required: true
schema:
description: An Id of the respository DTO elemennt
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Slide.Groups"
description: Success
"401":
description: Unauthorized
summary: "Groups: Get by Id"
tags:
- Groups
"/Images/ChildObjects/{id}":
get:
description: This endpoint is helpful for helping users and bots identify dependent objects to this Shared and retreive their corresponding metadata in order to make modifications to those objects in downstream operations.
operationId: shared_images_childobjects_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
items:
$ref: "#/components/schemas/ChildObjects"
type: array
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Forbidden
summary: "Shared: Get Dependent Objects Tree"
tags:
- Images
x-operationName: get-obj-tree
"/Images/Details/{id}":
get:
description: "Returns object metadata and information about relative dependent objects "
operationId: shared_images_details_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Shared.Pictures.Details"
description: Success
"401":
description: Unauthorized
summary: "Shared: Get Details"
tags:
- Images
x-operationName: details
"/Images/GetImage/{Id}":
put:
description: Download Images extracted from Ooxml Documents
operationId: shared_images_getimage_put_id
parameters:
- description: The Image Id
in: path
name: Id
required: true
schema:
description: The Image Id
format: uuid
type: string
responses:
"200":
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"504":
description: Server Error
summary: "Image: Download Image"
tags:
- Images
"/Images/OpenOfficeXml/{id}":
get:
description: Return the subset of the xml content from within the latest edited version of the OpenXmlDocument from this Shared object. The returned xml data conforms to the [Ecma-376 standard](http://www.ecma-international.org/publications/standards/Ecma-376.htm). Use this endpoint a starting point for building client-side extensions that modify Presalytics widgets containing Shared objects.
operationId: shared_images_openofficexml_get_id_updated
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
- description: Indicates whether API should return the orginal uploaded xml (false) or the actively updated version (true, default)
in: query
name: updated
schema:
default: true
type: boolean
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
summary: "Shared: Get Underlying Xml"
tags:
- Images
x-operationName: get-xml
put:
description: Directly eidt the underlying xml of a Shared object within an OpenOpenXml (e.g. Excel, Powerpoint) document. This endpoint will validatate the submitted xml against the [Ecma-376 standard](http://www.ecma-international.org/publications/standards/Ecma-376.htm) prior to saving modification. Invalid xml will rejected by this endpoint and a (hopefully) helpful error message will be returned. Use this endpoint for client-side automation of modifications ot Shared objects.
operationId: shared_images_openofficexml_put_id
parameters:
- in: path
name: id
required: true
schema:
type: string
requestBody:
content:
application/*+json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
application/json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
application/json-patch+json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
text/json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
responses:
"200":
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
summary: "Shared: Modify Underlying Xml"
tags:
- Images
x-operationName: put-xml
"/Images/Svg/{id}":
get:
description: This endpoint is helpful for rending this Shared as an svg or image object that can then be rendered in a story, dashboard or website.
operationId: shared_images_svg_get_id_use_cache
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
- description: Indicates whether API should retrieve content from a cache if aviable (true, default), or force an update (false)
in: query
name: use_cache
schema:
default: false
type: boolean
responses:
"200":
content:
image/svg+xml:
schema:
description: The file byte stream.
format: binary
title: Svg byte stream
type: string
description: Returns an svg formatted-image of this object.
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
summary: "Shared: Get Svg file"
tags:
- Images
x-operationName: get-svg
"/Images/{id}":
get:
description: "Get by Id: Use this method to retrieve a Images object by its primary key (id)"
operationId: shared_images_get_id
parameters:
- description: An Id of the respository DTO elemennt
in: path
name: id
required: true
schema:
description: An Id of the respository DTO elemennt
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Shared.Pictures"
description: Success
"401":
description: Unauthorized
summary: "Images: Get by Id"
tags:
- Images
"/ShapeTrees/ChildObjects/{id}":
get:
description: This endpoint is helpful for helping users and bots identify dependent objects to this Slide and retreive their corresponding metadata in order to make modifications to those objects in downstream operations.
operationId: slides_shapetrees_childobjects_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
items:
$ref: "#/components/schemas/ChildObjects"
type: array
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Forbidden
summary: "Slides: Get Dependent Objects Tree"
tags:
- ShapeTrees
x-operationName: get-obj-tree
"/ShapeTrees/Details/{id}":
get:
description: "Returns object metadata and information about relative dependent objects "
operationId: slides_shapetrees_details_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Slide.ShapeTrees.Details"
description: Success
"401":
description: Unauthorized
summary: "Slides: Get Details"
tags:
- ShapeTrees
x-operationName: details
"/ShapeTrees/OpenOfficeXml/{id}":
get:
description: Return the subset of the xml content from within the latest edited version of the OpenXmlDocument from this Slide object. The returned xml data conforms to the [Ecma-376 standard](http://www.ecma-international.org/publications/standards/Ecma-376.htm). Use this endpoint a starting point for building client-side extensions that modify Presalytics widgets containing Slide objects.
operationId: slides_shapetrees_openofficexml_get_id_updated
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
- description: Indicates whether API should return the orginal uploaded xml (false) or the actively updated version (true, default)
in: query
name: updated
schema:
default: true
type: boolean
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
summary: "Slides: Get Underlying Xml"
tags:
- ShapeTrees
x-operationName: get-xml
put:
description: Directly eidt the underlying xml of a Slide object within an OpenOpenXml (e.g. Excel, Powerpoint) document. This endpoint will validatate the submitted xml against the [Ecma-376 standard](http://www.ecma-international.org/publications/standards/Ecma-376.htm) prior to saving modification. Invalid xml will rejected by this endpoint and a (hopefully) helpful error message will be returned. Use this endpoint for client-side automation of modifications ot Slide objects.
operationId: slides_shapetrees_openofficexml_put_id
parameters:
- in: path
name: id
required: true
schema:
type: string
requestBody:
content:
application/*+json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
application/json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
application/json-patch+json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
text/json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
responses:
"200":
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
summary: "Slides: Modify Underlying Xml"
tags:
- ShapeTrees
x-operationName: put-xml
"/ShapeTrees/Svg/{id}":
get:
description: This endpoint is helpful for rending this Slide as an svg or image object that can then be rendered in a story, dashboard or website.
operationId: slides_shapetrees_svg_get_id_use_cache
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
- description: Indicates whether API should retrieve content from a cache if aviable (true, default), or force an update (false)
in: query
name: use_cache
schema:
default: false
type: boolean
responses:
"200":
content:
image/svg+xml:
schema:
description: The file byte stream.
format: binary
title: Svg byte stream
type: string
description: Returns an svg formatted-image of this object.
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
summary: "Slides: Get Svg file"
tags:
- ShapeTrees
x-operationName: get-svg
"/ShapeTrees/{id}":
get:
description: "Get by Id: Use this method to retrieve a ShapeTrees object by its primary key (id)"
operationId: slides_shapetrees_get_id
parameters:
- description: An Id of the respository DTO elemennt
in: path
name: id
required: true
schema:
description: An Id of the respository DTO elemennt
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Slide.ShapeTrees"
description: Success
"401":
description: Unauthorized
summary: "ShapeTrees: Get by Id"
tags:
- ShapeTrees
"/Shapes/ChildObjects/{id}":
get:
description: This endpoint is helpful for helping users and bots identify dependent objects to this Slide and retreive their corresponding metadata in order to make modifications to those objects in downstream operations.
operationId: slides_shapes_childobjects_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
items:
$ref: "#/components/schemas/ChildObjects"
type: array
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Forbidden
summary: "Slides: Get Dependent Objects Tree"
tags:
- Shapes
x-operationName: get-obj-tree
"/Shapes/Details/{id}":
get:
description: "Returns object metadata and information about relative dependent objects "
operationId: slides_shapes_details_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Slide.Shapes.Details"
description: Success
"401":
description: Unauthorized
summary: "Slides: Get Details"
tags:
- Shapes
x-operationName: details
"/Shapes/OpenOfficeXml/{id}":
get:
description: Return the subset of the xml content from within the latest edited version of the OpenXmlDocument from this Slide object. The returned xml data conforms to the [Ecma-376 standard](http://www.ecma-international.org/publications/standards/Ecma-376.htm). Use this endpoint a starting point for building client-side extensions that modify Presalytics widgets containing Slide objects.
operationId: slides_shapes_openofficexml_get_id_updated
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
- description: Indicates whether API should return the orginal uploaded xml (false) or the actively updated version (true, default)
in: query
name: updated
schema:
default: true
type: boolean
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
summary: "Slides: Get Underlying Xml"
tags:
- Shapes
x-operationName: get-xml
put:
description: Directly eidt the underlying xml of a Slide object within an OpenOpenXml (e.g. Excel, Powerpoint) document. This endpoint will validatate the submitted xml against the [Ecma-376 standard](http://www.ecma-international.org/publications/standards/Ecma-376.htm) prior to saving modification. Invalid xml will rejected by this endpoint and a (hopefully) helpful error message will be returned. Use this endpoint for client-side automation of modifications ot Slide objects.
operationId: slides_shapes_openofficexml_put_id
parameters:
- in: path
name: id
required: true
schema:
type: string
requestBody:
content:
application/*+json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
application/json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
application/json-patch+json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
text/json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
responses:
"200":
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
summary: "Slides: Modify Underlying Xml"
tags:
- Shapes
x-operationName: put-xml
"/Shapes/Svg/{id}":
get:
description: This endpoint is helpful for rending this Slide as an svg or image object that can then be rendered in a story, dashboard or website.
operationId: slides_shapes_svg_get_id_use_cache
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
- description: Indicates whether API should retrieve content from a cache if aviable (true, default), or force an update (false)
in: query
name: use_cache
schema:
default: false
type: boolean
responses:
"200":
content:
image/svg+xml:
schema:
description: The file byte stream.
format: binary
title: Svg byte stream
type: string
description: Returns an svg formatted-image of this object.
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
summary: "Slides: Get Svg file"
tags:
- Shapes
x-operationName: get-svg
"/Shapes/{id}":
get:
description: "Get by Id: Use this method to retrieve a Shapes object by its primary key (id)"
operationId: slides_shapes_get_id
parameters:
- description: An Id of the respository DTO elemennt
in: path
name: id
required: true
schema:
description: An Id of the respository DTO elemennt
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Slide.Shapes"
description: Success
"401":
description: Unauthorized
summary: "Shapes: Get by Id"
tags:
- Shapes
"/Shared/ColorTransformationAttributes/{id}":
get:
description: "Get by Id: Use this method to retrieve a ColorTransformationAttributes object by its primary key (id)"
operationId: shared_colortransformationattributes_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Shared.ColorTransformationAttributes"
description: Success
"401":
description: Unauthorized
summary: "ColorTransformationAttributes: Get by Id"
tags:
- Shared/ColorTransformationAttributes
"/Shared/ColorTransformations/{id}":
get:
description: "Get by Id: Use this method to retrieve a ColorTransformations object by its primary key (id)"
operationId: shared_colortransformations_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Shared.ColorTransformations"
description: Success
"401":
description: Unauthorized
summary: "ColorTransformations: Get by Id"
tags:
- Shared/ColorTransformations
/Shared/ColorTypes:
get:
description: "List Types: Use this method to retreive a list of possible options for the ColorTypes type. Use the Id from oneof the returned elements on to make changes to elements in the Shared object space."
operationId: shared_colortypes_get
responses:
"200":
content:
application/json:
schema:
items:
$ref: "#/components/schemas/Shared.ColorTypes"
type: array
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "ColorTypes: List All Possible Types"
tags:
- Shared/ColorTypes
x-operationName: list
"/Shared/ColorTypes/TypeId/{type_id}":
get:
description: This endpoint returns Type metadata from an integer type_id that can found on objects throughout the api.
operationId: shared_colortypes_typeid_get_type_id
parameters:
- in: path
name: type_id
required: true
schema:
format: int32
type: integer
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Shared.ColorTypes"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "ColorTypes: Get By Type Id"
tags:
- Shared/ColorTypes
x-operationName: type-id
"/Shared/ColorTypes/{id}":
get:
description: "Get by Id: Use this method to retrieve a ColorTypes object by its primary key (id)"
operationId: shared_colortypes_get_id
parameters:
- description: ""
in: path
name: id
required: true
schema:
description: ""
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Shared.ColorTypes"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "ColorTypes: Get by Id"
tags:
- Shared/ColorTypes
/Shared/DashTypes:
get:
description: "List Types: Use this method to retreive a list of possible options for the DashTypes type. Use the Id from oneof the returned elements on to make changes to elements in the Shared object space."
operationId: shared_dashtypes_get
responses:
"200":
content:
application/json:
schema:
items:
$ref: "#/components/schemas/Shared.DashTypes"
type: array
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "DashTypes: List All Possible Types"
tags:
- Shared/DashTypes
x-operationName: list
"/Shared/DashTypes/TypeId/{type_id}":
get:
description: This endpoint returns Type metadata from an integer type_id that can found on objects throughout the api.
operationId: shared_dashtypes_typeid_get_type_id
parameters:
- in: path
name: type_id
required: true
schema:
format: int32
type: integer
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Shared.DashTypes"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "DashTypes: Get By Type Id"
tags:
- Shared/DashTypes
x-operationName: type-id
"/Shared/DashTypes/{id}":
get:
description: "Get by Id: Use this method to retrieve a DashTypes object by its primary key (id)"
operationId: shared_dashtypes_get_id
parameters:
- description: ""
in: path
name: id
required: true
schema:
description: ""
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Shared.DashTypes"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "DashTypes: Get by Id"
tags:
- Shared/DashTypes
"/Shared/EffectAttributes/{id}":
get:
description: "Get by Id: Use this method to retrieve a EffectAttributes object by its primary key (id)"
operationId: shared_effectattributes_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Shared.EffectAttributes"
description: Success
"401":
description: Unauthorized
summary: "EffectAttributes: Get by Id"
tags:
- Shared/EffectAttributes
/Shared/EffectTypes:
get:
description: "List Types: Use this method to retreive a list of possible options for the EffectTypes type. Use the Id from oneof the returned elements on to make changes to elements in the Shared object space."
operationId: shared_effecttypes_get
responses:
"200":
content:
application/json:
schema:
items:
$ref: "#/components/schemas/Shared.EffectTypes"
type: array
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "EffectTypes: List All Possible Types"
tags:
- Shared/EffectTypes
x-operationName: list
"/Shared/EffectTypes/TypeId/{type_id}":
get:
description: This endpoint returns Type metadata from an integer type_id that can found on objects throughout the api.
operationId: shared_effecttypes_typeid_get_type_id
parameters:
- in: path
name: type_id
required: true
schema:
format: int32
type: integer
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Shared.EffectTypes"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "EffectTypes: Get By Type Id"
tags:
- Shared/EffectTypes
x-operationName: type-id
"/Shared/EffectTypes/{id}":
get:
description: "Get by Id: Use this method to retrieve a EffectTypes object by its primary key (id)"
operationId: shared_effecttypes_get_id
parameters:
- description: ""
in: path
name: id
required: true
schema:
description: ""
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Shared.EffectTypes"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "EffectTypes: Get by Id"
tags:
- Shared/EffectTypes
"/Shared/Effects/{id}":
get:
description: "Get by Id: Use this method to retrieve a Effects object by its primary key (id)"
operationId: shared_effects_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Shared.Effects"
description: Success
"401":
description: Unauthorized
summary: "Effects: Get by Id"
tags:
- Shared/Effects
"/Shared/FillMap/{id}":
get:
description: "Get by Id: Use this method to retrieve a FillMap object by its primary key (id)"
operationId: shared_fillmap_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Shared.FillMap"
description: Success
"401":
description: Unauthorized
summary: "FillMap: Get by Id"
tags:
- Shared/FillMap
/Shared/FillTypes:
get:
description: "List Types: Use this method to retreive a list of possible options for the FillTypes type. Use the Id from oneof the returned elements on to make changes to elements in the Shared object space."
operationId: shared_filltypes_get
responses:
"200":
content:
application/json:
schema:
items:
$ref: "#/components/schemas/Shared.FillTypes"
type: array
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "FillTypes: List All Possible Types"
tags:
- Shared/FillTypes
x-operationName: list
"/Shared/FillTypes/TypeId/{type_id}":
get:
description: This endpoint returns Type metadata from an integer type_id that can found on objects throughout the api.
operationId: shared_filltypes_typeid_get_type_id
parameters:
- in: path
name: type_id
required: true
schema:
format: int32
type: integer
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Shared.FillTypes"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "FillTypes: Get By Type Id"
tags:
- Shared/FillTypes
x-operationName: type-id
"/Shared/FillTypes/{id}":
get:
description: "Get by Id: Use this method to retrieve a FillTypes object by its primary key (id)"
operationId: shared_filltypes_get_id
parameters:
- description: ""
in: path
name: id
required: true
schema:
description: ""
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Shared.FillTypes"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "FillTypes: Get by Id"
tags:
- Shared/FillTypes
"/Shared/GradientFills/{id}":
get:
description: "Get by Id: Use this method to retrieve a GradientFills object by its primary key (id)"
operationId: shared_gradientfills_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Shared.GradientFills"
description: Success
"401":
description: Unauthorized
summary: "GradientFills: Get by Id"
tags:
- Shared/GradientFills
"/Shared/GradientStops/{id}":
get:
description: "Get by Id: Use this method to retrieve a GradientStops object by its primary key (id)"
operationId: shared_gradientstops_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Shared.GradientStops"
description: Success
"401":
description: Unauthorized
summary: "GradientStops: Get by Id"
tags:
- Shared/GradientStops
"/Shared/ImageFills/{id}":
get:
description: "Get by Id: Use this method to retrieve a ImageFills object by its primary key (id)"
operationId: shared_imagefills_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Shared.ImageFills"
description: Success
"401":
description: Unauthorized
summary: "ImageFills: Get by Id"
tags:
- Shared/ImageFills
/Shared/LineEndSizes:
get:
description: "List Types: Use this method to retreive a list of possible options for the LineEndSizes type. Use the Id from oneof the returned elements on to make changes to elements in the Shared object space."
operationId: shared_lineendsizes_get
responses:
"200":
content:
application/json:
schema:
items:
$ref: "#/components/schemas/Shared.LineEndSizes"
type: array
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "LineEndSizes: List All Possible Types"
tags:
- Shared/LineEndSizes
x-operationName: list
"/Shared/LineEndSizes/TypeId/{type_id}":
get:
description: This endpoint returns Type metadata from an integer type_id that can found on objects throughout the api.
operationId: shared_lineendsizes_typeid_get_type_id
parameters:
- in: path
name: type_id
required: true
schema:
format: int32
type: integer
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Shared.LineEndSizes"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "LineEndSizes: Get By Type Id"
tags:
- Shared/LineEndSizes
x-operationName: type-id
"/Shared/LineEndSizes/{id}":
get:
description: "Get by Id: Use this method to retrieve a LineEndSizes object by its primary key (id)"
operationId: shared_lineendsizes_get_id
parameters:
- description: ""
in: path
name: id
required: true
schema:
description: ""
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Shared.LineEndSizes"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "LineEndSizes: Get by Id"
tags:
- Shared/LineEndSizes
/Shared/LineEndTypes:
get:
description: "List Types: Use this method to retreive a list of possible options for the LineEndTypes type. Use the Id from oneof the returned elements on to make changes to elements in the Shared object space."
operationId: shared_lineendtypes_get
responses:
"200":
content:
application/json:
schema:
items:
$ref: "#/components/schemas/Shared.LineEndTypes"
type: array
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "LineEndTypes: List All Possible Types"
tags:
- Shared/LineEndTypes
x-operationName: list
"/Shared/LineEndTypes/TypeId/{type_id}":
get:
description: This endpoint returns Type metadata from an integer type_id that can found on objects throughout the api.
operationId: shared_lineendtypes_typeid_get_type_id
parameters:
- in: path
name: type_id
required: true
schema:
format: int32
type: integer
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Shared.LineEndTypes"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "LineEndTypes: Get By Type Id"
tags:
- Shared/LineEndTypes
x-operationName: type-id
"/Shared/LineEndTypes/{id}":
get:
description: "Get by Id: Use this method to retrieve a LineEndTypes object by its primary key (id)"
operationId: shared_lineendtypes_get_id
parameters:
- description: ""
in: path
name: id
required: true
schema:
description: ""
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Shared.LineEndTypes"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "LineEndTypes: Get by Id"
tags:
- Shared/LineEndTypes
"/Shared/Lines/{id}":
get:
description: "Get by Id: Use this method to retrieve a Lines object by its primary key (id)"
operationId: shared_lines_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Shared.Lines"
description: Success
"401":
description: Unauthorized
summary: "Lines: Get by Id"
tags:
- Shared/Lines
"/Shared/Paragraph/{id}":
get:
description: "Get by Id: Use this method to retrieve a Paragraph object by its primary key (id)"
operationId: shared_paragraph_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Shared.Paragraph"
description: Success
"401":
description: Unauthorized
summary: "Paragraph: Get by Id"
tags:
- Shared/Paragraph
"/Shared/SolidFills/{id}":
get:
description: "Get by Id: Use this method to retrieve a SolidFills object by its primary key (id)"
operationId: shared_solidfills_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Shared.SolidFills"
description: Success
"401":
description: Unauthorized
summary: "SolidFills: Get by Id"
tags:
- Shared/SolidFills
"/Shared/Text/{id}":
get:
description: "Get by Id: Use this method to retrieve a Text object by its primary key (id)"
operationId: shared_text_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Shared.Text"
description: Success
"401":
description: Unauthorized
summary: "Text: Get by Id"
tags:
- Shared/Text
"/Shared/TextContainer/{id}":
get:
description: "Get by Id: Use this method to retrieve a TextContainer object by its primary key (id)"
operationId: shared_textcontainer_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Shared.TextContainer"
description: Success
"401":
description: Unauthorized
summary: "TextContainer: Get by Id"
tags:
- Shared/TextContainer
"/Slides/ChildObjects/{id}":
get:
description: This endpoint is helpful for helping users and bots identify dependent objects to this Slide and retreive their corresponding metadata in order to make modifications to those objects in downstream operations.
operationId: slides_slides_childobjects_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
items:
$ref: "#/components/schemas/ChildObjects"
type: array
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Forbidden
summary: "Slides: Get Dependent Objects Tree"
tags:
- Slides
x-operationName: get-obj-tree
"/Slides/ColorMaps/{id}":
get:
description: "Get by Id: Use this method to retrieve a ColorMaps object by its primary key (id)"
operationId: slides_colormaps_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Slide.ColorMaps"
description: Success
"401":
description: Unauthorized
summary: "ColorMaps: Get by Id"
tags:
- Slides/ColorMaps
"/Slides/Details/{id}":
get:
description: "Returns object metadata and information about relative dependent objects "
operationId: slides_slides_details_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Slide.Slides.Details"
description: Success
"401":
description: Unauthorized
summary: "Slides: Get Details"
tags:
- Slides
x-operationName: details
/Slides/GraphicTypes:
get:
description: "List Types: Use this method to retreive a list of possible options for the GraphicTypes type. Use the Id from oneof the returned elements on to make changes to elements in the Slides object space."
operationId: slides_graphictypes_get
responses:
"200":
content:
application/json:
schema:
items:
$ref: "#/components/schemas/Slide.GraphicTypes"
type: array
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "GraphicTypes: List All Possible Types"
tags:
- Slides/GraphicTypes
x-operationName: list
"/Slides/GraphicTypes/TypeId/{type_id}":
get:
description: This endpoint returns Type metadata from an integer type_id that can found on objects throughout the api.
operationId: slides_graphictypes_typeid_get_type_id
parameters:
- in: path
name: type_id
required: true
schema:
format: int32
type: integer
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Slide.GraphicTypes"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "GraphicTypes: Get By Type Id"
tags:
- Slides/GraphicTypes
x-operationName: type-id
"/Slides/GraphicTypes/{id}":
get:
description: "Get by Id: Use this method to retrieve a GraphicTypes object by its primary key (id)"
operationId: slides_graphictypes_get_id
parameters:
- description: ""
in: path
name: id
required: true
schema:
description: ""
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Slide.GraphicTypes"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "GraphicTypes: Get by Id"
tags:
- Slides/GraphicTypes
"/Slides/Graphics/{id}":
get:
description: "Get by Id: Use this method to retrieve a Graphics object by its primary key (id)"
operationId: slides_graphics_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Slide.Graphics"
description: Success
"401":
description: Unauthorized
summary: "Graphics: Get by Id"
tags:
- Slides/Graphics
/Slides/GroupElementTypes:
get:
description: "List Types: Use this method to retreive a list of possible options for the GroupElementTypes type. Use the Id from oneof the returned elements on to make changes to elements in the Slides object space."
operationId: slides_groupelementtypes_get
responses:
"200":
content:
application/json:
schema:
items:
$ref: "#/components/schemas/Slide.GroupElementTypes"
type: array
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "GroupElementTypes: List All Possible Types"
tags:
- Slides/GroupElementTypes
x-operationName: list
"/Slides/GroupElementTypes/TypeId/{type_id}":
get:
description: This endpoint returns Type metadata from an integer type_id that can found on objects throughout the api.
operationId: slides_groupelementtypes_typeid_get_type_id
parameters:
- in: path
name: type_id
required: true
schema:
format: int32
type: integer
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Slide.GroupElementTypes"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "GroupElementTypes: Get By Type Id"
tags:
- Slides/GroupElementTypes
x-operationName: type-id
"/Slides/GroupElementTypes/{id}":
get:
description: "Get by Id: Use this method to retrieve a GroupElementTypes object by its primary key (id)"
operationId: slides_groupelementtypes_get_id
parameters:
- description: ""
in: path
name: id
required: true
schema:
description: ""
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Slide.GroupElementTypes"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "GroupElementTypes: Get by Id"
tags:
- Slides/GroupElementTypes
"/Slides/GroupElements/{id}":
get:
description: "Get by Id: Use this method to retrieve a GroupElements object by its primary key (id)"
operationId: slides_groupelements_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Slide.GroupElements"
description: Success
"401":
description: Unauthorized
summary: "GroupElements: Get by Id"
tags:
- Slides/GroupElements
"/Slides/OpenOfficeXml/{id}":
get:
description: Return the subset of the xml content from within the latest edited version of the OpenXmlDocument from this Slide object. The returned xml data conforms to the [Ecma-376 standard](http://www.ecma-international.org/publications/standards/Ecma-376.htm). Use this endpoint a starting point for building client-side extensions that modify Presalytics widgets containing Slide objects.
operationId: slides_slides_openofficexml_get_id_updated
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
- description: Indicates whether API should return the orginal uploaded xml (false) or the actively updated version (true, default)
in: query
name: updated
schema:
default: true
type: boolean
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
summary: "Slides: Get Underlying Xml"
tags:
- Slides
x-operationName: get-xml
put:
description: Directly eidt the underlying xml of a Slide object within an OpenOpenXml (e.g. Excel, Powerpoint) document. This endpoint will validatate the submitted xml against the [Ecma-376 standard](http://www.ecma-international.org/publications/standards/Ecma-376.htm) prior to saving modification. Invalid xml will rejected by this endpoint and a (hopefully) helpful error message will be returned. Use this endpoint for client-side automation of modifications ot Slide objects.
operationId: slides_slides_openofficexml_put_id
parameters:
- in: path
name: id
required: true
schema:
type: string
requestBody:
content:
application/*+json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
application/json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
application/json-patch+json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
text/json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
responses:
"200":
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
summary: "Slides: Modify Underlying Xml"
tags:
- Slides
x-operationName: put-xml
"/Slides/SlideMasters/{id}":
get:
description: "Get by Id: Use this method to retrieve a SlideMasters object by its primary key (id)"
operationId: slides_slidemasters_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Slide.SlideMasters"
description: Success
"401":
description: Unauthorized
summary: "SlideMasters: Get by Id"
tags:
- Slides/SlideMasters
"/Slides/Svg/{id}":
get:
description: This endpoint is helpful for rending this Slide as an svg or image object that can then be rendered in a story, dashboard or website.
operationId: slides_slides_svg_get_id_use_cache
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
- description: Indicates whether API should retrieve content from a cache if aviable (true, default), or force an update (false)
in: query
name: use_cache
schema:
default: false
type: boolean
responses:
"200":
content:
image/svg+xml:
schema:
description: The file byte stream.
format: binary
title: Svg byte stream
type: string
description: Returns an svg formatted-image of this object.
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
summary: "Slides: Get Svg file"
tags:
- Slides
x-operationName: get-svg
"/Slides/{id}":
get:
description: "Get by Id: Use this method to retrieve a Slides object by its primary key (id)"
operationId: slides_slides_get_id
parameters:
- description: An Id of the respository DTO elemennt
in: path
name: id
required: true
schema:
description: An Id of the respository DTO elemennt
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Slide.Slides"
description: Success
"401":
description: Unauthorized
summary: "Slides: Get by Id"
tags:
- Slides
"/SmartArts/ChildObjects/{id}":
get:
description: This endpoint is helpful for helping users and bots identify dependent objects to this Slide and retreive their corresponding metadata in order to make modifications to those objects in downstream operations.
operationId: slides_smartarts_childobjects_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
items:
$ref: "#/components/schemas/ChildObjects"
type: array
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Forbidden
summary: "Slides: Get Dependent Objects Tree"
tags:
- SmartArts
x-operationName: get-obj-tree
"/SmartArts/Details/{id}":
get:
description: "Returns object metadata and information about relative dependent objects "
operationId: slides_smartarts_details_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Slide.SmartArts.Details"
description: Success
"401":
description: Unauthorized
summary: "Slides: Get Details"
tags:
- SmartArts
x-operationName: details
"/SmartArts/OpenOfficeXml/{id}":
get:
description: Return the subset of the xml content from within the latest edited version of the OpenXmlDocument from this Slide object. The returned xml data conforms to the [Ecma-376 standard](http://www.ecma-international.org/publications/standards/Ecma-376.htm). Use this endpoint a starting point for building client-side extensions that modify Presalytics widgets containing Slide objects.
operationId: slides_smartarts_openofficexml_get_id_updated
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
- description: Indicates whether API should return the orginal uploaded xml (false) or the actively updated version (true, default)
in: query
name: updated
schema:
default: true
type: boolean
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
summary: "Slides: Get Underlying Xml"
tags:
- SmartArts
x-operationName: get-xml
put:
description: Directly eidt the underlying xml of a Slide object within an OpenOpenXml (e.g. Excel, Powerpoint) document. This endpoint will validatate the submitted xml against the [Ecma-376 standard](http://www.ecma-international.org/publications/standards/Ecma-376.htm) prior to saving modification. Invalid xml will rejected by this endpoint and a (hopefully) helpful error message will be returned. Use this endpoint for client-side automation of modifications ot Slide objects.
operationId: slides_smartarts_openofficexml_put_id
parameters:
- in: path
name: id
required: true
schema:
type: string
requestBody:
content:
application/*+json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
application/json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
application/json-patch+json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
text/json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
responses:
"200":
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
summary: "Slides: Modify Underlying Xml"
tags:
- SmartArts
x-operationName: put-xml
"/SmartArts/Svg/{id}":
get:
description: This endpoint is helpful for rending this Slide as an svg or image object that can then be rendered in a story, dashboard or website.
operationId: slides_smartarts_svg_get_id_use_cache
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
- description: Indicates whether API should retrieve content from a cache if aviable (true, default), or force an update (false)
in: query
name: use_cache
schema:
default: false
type: boolean
responses:
"200":
content:
image/svg+xml:
schema:
description: The file byte stream.
format: binary
title: Svg byte stream
type: string
description: Returns an svg formatted-image of this object.
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
summary: "Slides: Get Svg file"
tags:
- SmartArts
x-operationName: get-svg
"/SmartArts/{id}":
get:
description: "Get by Id: Use this method to retrieve a SmartArts object by its primary key (id)"
operationId: slides_smartarts_get_id
parameters:
- description: An Id of the respository DTO elemennt
in: path
name: id
required: true
schema:
description: An Id of the respository DTO elemennt
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Slide.SmartArts"
description: Success
"401":
description: Unauthorized
summary: "SmartArts: Get by Id"
tags:
- SmartArts
"/Tables/Borders/{id}":
get:
description: "Get by Id: Use this method to retrieve a Borders object by its primary key (id)"
operationId: tables_borders_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Table.Borders"
description: Success
"401":
description: Unauthorized
summary: "Borders: Get by Id"
tags:
- Tables/Borders
"/Tables/Cells/{id}":
get:
description: "Get by Id: Use this method to retrieve a Cells object by its primary key (id)"
operationId: tables_cells_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Table.Cells"
description: Success
"401":
description: Unauthorized
summary: "Cells: Get by Id"
tags:
- Tables/Cells
"/Tables/ChildObjects/{id}":
get:
description: This endpoint is helpful for helping users and bots identify dependent objects to this Table and retreive their corresponding metadata in order to make modifications to those objects in downstream operations.
operationId: tables_tables_childobjects_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
items:
$ref: "#/components/schemas/ChildObjects"
type: array
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Forbidden
summary: "Tables: Get Dependent Objects Tree"
tags:
- Tables
x-operationName: get-obj-tree
"/Tables/Columns/{id}":
get:
description: "Get by Id: Use this method to retrieve a Columns object by its primary key (id)"
operationId: tables_columns_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Table.Columns"
description: Success
"401":
description: Unauthorized
summary: "Columns: Get by Id"
tags:
- Tables/Columns
"/Tables/Details/{id}":
get:
description: "Returns object metadata and information about relative dependent objects "
operationId: tables_tables_details_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Table.Tables.Details"
description: Success
"401":
description: Unauthorized
summary: "Tables: Get Details"
tags:
- Tables
x-operationName: details
"/Tables/OpenOfficeXml/{id}":
get:
description: Return the subset of the xml content from within the latest edited version of the OpenXmlDocument from this Table object. The returned xml data conforms to the [Ecma-376 standard](http://www.ecma-international.org/publications/standards/Ecma-376.htm). Use this endpoint a starting point for building client-side extensions that modify Presalytics widgets containing Table objects.
operationId: tables_tables_openofficexml_get_id_updated
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
- description: Indicates whether API should return the orginal uploaded xml (false) or the actively updated version (true, default)
in: query
name: updated
schema:
default: true
type: boolean
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
summary: "Tables: Get Underlying Xml"
tags:
- Tables
x-operationName: get-xml
put:
description: Directly eidt the underlying xml of a Table object within an OpenOpenXml (e.g. Excel, Powerpoint) document. This endpoint will validatate the submitted xml against the [Ecma-376 standard](http://www.ecma-international.org/publications/standards/Ecma-376.htm) prior to saving modification. Invalid xml will rejected by this endpoint and a (hopefully) helpful error message will be returned. Use this endpoint for client-side automation of modifications ot Table objects.
operationId: tables_tables_openofficexml_put_id
parameters:
- in: path
name: id
required: true
schema:
type: string
requestBody:
content:
application/*+json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
application/json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
application/json-patch+json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
text/json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
responses:
"200":
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
summary: "Tables: Modify Underlying Xml"
tags:
- Tables
x-operationName: put-xml
"/Tables/Rows/{id}":
get:
description: "Get by Id: Use this method to retrieve a Rows object by its primary key (id)"
operationId: tables_rows_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Table.Rows"
description: Success
"401":
description: Unauthorized
summary: "Rows: Get by Id"
tags:
- Tables/Rows
"/Tables/Svg/{id}":
get:
description: This endpoint is helpful for rending this Table as an svg or image object that can then be rendered in a story, dashboard or website.
operationId: tables_tables_svg_get_id_use_cache
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
- description: Indicates whether API should retrieve content from a cache if aviable (true, default), or force an update (false)
in: query
name: use_cache
schema:
default: false
type: boolean
responses:
"200":
content:
image/svg+xml:
schema:
description: The file byte stream.
format: binary
title: Svg byte stream
type: string
description: Returns an svg formatted-image of this object.
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
summary: "Tables: Get Svg file"
tags:
- Tables
x-operationName: get-svg
"/Tables/TableUpdate/{id}":
get:
description: Gets a TabletDataDTO object, usually used for downstream updates to table content
operationId: tables_tables_tableupdate_get_id
parameters:
- description: ""
in: path
name: id
required: true
schema:
description: ""
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Table.TableDataDTO"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "Table: Get Table Data"
tags:
- Tables
put:
description: Simplifies table update by allowing users to supply strings to table cells via TableDataDTO
operationId: tables_tables_tableupdate_put_id
parameters:
- in: path
name: id
required: true
schema:
type: string
requestBody:
content:
application/*+json:
schema:
$ref: "#/components/schemas/Table.TableDataDTO"
application/json:
schema:
$ref: "#/components/schemas/Table.TableDataDTO"
application/json-patch+json:
schema:
$ref: "#/components/schemas/Table.TableDataDTO"
text/json:
schema:
$ref: "#/components/schemas/Table.TableDataDTO"
description: ""
responses:
"200":
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
summary: "Tables: Update Table Data"
tags:
- Tables
"/Tables/{id}":
get:
description: "Get by Id: Use this method to retrieve a Tables object by its primary key (id)"
operationId: tables_tables_get_id
parameters:
- description: An Id of the respository DTO elemennt
in: path
name: id
required: true
schema:
description: An Id of the respository DTO elemennt
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Table.Tables"
description: Success
"401":
description: Unauthorized
summary: "Tables: Get by Id"
tags:
- Tables
"/Themes/BackgroundFills/{id}":
get:
description: "Get by Id: Use this method to retrieve a BackgroundFills object by its primary key (id)"
operationId: themes_backgroundfills_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Theme.BackgroundFills"
description: Success
"401":
description: Unauthorized
summary: "BackgroundFills: Get by Id"
tags:
- Themes/BackgroundFills
"/Themes/ChildObjects/{id}":
get:
description: This endpoint is helpful for helping users and bots identify dependent objects to this Theme and retreive their corresponding metadata in order to make modifications to those objects in downstream operations.
operationId: theme_themes_childobjects_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
items:
$ref: "#/components/schemas/ChildObjects"
type: array
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Forbidden
summary: "Theme: Get Dependent Objects Tree"
tags:
- Themes
x-operationName: get-obj-tree
"/Themes/Colors/{id}":
get:
description: "Get by Id: Use this method to retrieve a Colors object by its primary key (id)"
operationId: themes_colors_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Theme.Colors"
description: Success
"401":
description: Unauthorized
summary: "Colors: Get by Id"
tags:
- Themes/Colors
"/Themes/CustomColors/{id}":
get:
description: "Get by Id: Use this method to retrieve a CustomColors object by its primary key (id)"
operationId: themes_customcolors_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Theme.CustomColors"
description: Success
"401":
description: Unauthorized
summary: "CustomColors: Get by Id"
tags:
- Themes/CustomColors
"/Themes/Details/{id}":
get:
description: "Returns object metadata and information about relative dependent objects "
operationId: theme_themes_details_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Theme.Themes.Details"
description: Success
"401":
description: Unauthorized
summary: "Theme: Get Details"
tags:
- Themes
x-operationName: details
"/Themes/EffectMap/{id}":
get:
description: "Get by Id: Use this method to retrieve a EffectMap object by its primary key (id)"
operationId: themes_effectmap_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Theme.EffectMap"
description: Success
"401":
description: Unauthorized
summary: "EffectMap: Get by Id"
tags:
- Themes/EffectMap
"/Themes/Fills/{id}":
get:
description: "Get by Id: Use this method to retrieve a Fills object by its primary key (id)"
operationId: themes_fills_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Theme.Fills"
description: Success
"401":
description: Unauthorized
summary: "Fills: Get by Id"
tags:
- Themes/Fills
"/Themes/Fonts/{id}":
get:
description: "Get by Id: Use this method to retrieve a Fonts object by its primary key (id)"
operationId: themes_fonts_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Theme.Fonts"
description: Success
"401":
description: Unauthorized
summary: "Fonts: Get by Id"
tags:
- Themes/Fonts
/Themes/Intensity:
get:
description: "List Types: Use this method to retreive a list of possible options for the Intensity type. Use the Id from oneof the returned elements on to make changes to elements in the Themes object space."
operationId: themes_intensity_get
responses:
"200":
content:
application/json:
schema:
items:
$ref: "#/components/schemas/Theme.Intensity"
type: array
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "Intensity: List All Possible Types"
tags:
- Themes/Intensity
x-operationName: list
"/Themes/Intensity/TypeId/{type_id}":
get:
description: This endpoint returns Type metadata from an integer type_id that can found on objects throughout the api.
operationId: themes_intensity_typeid_get_type_id
parameters:
- in: path
name: type_id
required: true
schema:
format: int32
type: integer
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Theme.Intensity"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "Intensity: Get By Type Id"
tags:
- Themes/Intensity
x-operationName: type-id
"/Themes/Intensity/{id}":
get:
description: "Get by Id: Use this method to retrieve a Intensity object by its primary key (id)"
operationId: themes_intensity_get_id
parameters:
- description: ""
in: path
name: id
required: true
schema:
description: ""
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Theme.Intensity"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
text/plain:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
"401":
description: Unauthorized
summary: "Intensity: Get by Id"
tags:
- Themes/Intensity
"/Themes/LineMap/{id}":
get:
description: "Get by Id: Use this method to retrieve a LineMap object by its primary key (id)"
operationId: themes_linemap_get_id
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Theme.LineMap"
description: Success
"401":
description: Unauthorized
summary: "LineMap: Get by Id"
tags:
- Themes/LineMap
"/Themes/OpenOfficeXml/{id}":
get:
description: Return the subset of the xml content from within the latest edited version of the OpenXmlDocument from this Theme object. The returned xml data conforms to the [Ecma-376 standard](http://www.ecma-international.org/publications/standards/Ecma-376.htm). Use this endpoint a starting point for building client-side extensions that modify Presalytics widgets containing Theme objects.
operationId: theme_themes_openofficexml_get_id_updated
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
- description: Indicates whether API should return the orginal uploaded xml (false) or the actively updated version (true, default)
in: query
name: updated
schema:
default: true
type: boolean
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
summary: "Theme: Get Underlying Xml"
tags:
- Themes
x-operationName: get-xml
put:
description: Directly eidt the underlying xml of a Theme object within an OpenOpenXml (e.g. Excel, Powerpoint) document. This endpoint will validatate the submitted xml against the [Ecma-376 standard](http://www.ecma-international.org/publications/standards/Ecma-376.htm) prior to saving modification. Invalid xml will rejected by this endpoint and a (hopefully) helpful error message will be returned. Use this endpoint for client-side automation of modifications ot Theme objects.
operationId: theme_themes_openofficexml_put_id
parameters:
- in: path
name: id
required: true
schema:
type: string
requestBody:
content:
application/*+json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
application/json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
application/json-patch+json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
text/json:
schema:
$ref: "#/components/schemas/OoxmlDTO"
responses:
"200":
description: Success
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
summary: "Theme: Modify Underlying Xml"
tags:
- Themes
x-operationName: put-xml
"/Themes/Svg/{id}":
get:
description: This endpoint is helpful for rending this Theme as an svg or image object that can then be rendered in a story, dashboard or website.
operationId: theme_themes_svg_get_id_use_cache
parameters:
- in: path
name: id
required: true
schema:
format: uuid
type: string
- description: Indicates whether API should retrieve content from a cache if aviable (true, default), or force an update (false)
in: query
name: use_cache
schema:
default: false
type: boolean
responses:
"200":
content:
image/svg+xml:
schema:
description: The file byte stream.
format: binary
title: Svg byte stream
type: string
description: Returns an svg formatted-image of this object.
"400":
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
description: Bad Request
summary: "Theme: Get Svg file"
tags:
- Themes
x-operationName: get-svg
"/Themes/{id}":
get:
description: "Get by Id: Use this method to retrieve a Themes object by its primary key (id)"
operationId: theme_themes_get_id
parameters:
- description: An Id of the respository DTO elemennt
in: path
name: id
required: true
schema:
description: An Id of the respository DTO elemennt
format: uuid
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Theme.Themes"
description: Success
"401":
description: Unauthorized
summary: "Themes: Get by Id"
tags:
- Themes
components:
schemas:
Chart.Axes:
properties:
axisDataTypeId:
format: int32
title: Chart.Axes
type: integer
chartsId:
format: uuid
title: Chart.Axes
type: string
id:
format: uuid
title: Chart.Axes
type: string
ooxmlId:
format: int32
title: Chart.Axes
type: integer
type: object
Chart.Axes.Details:
properties:
axisDataTypeId:
format: int32
title: Chart.Axes.Details
type: integer
chart:
$ref: "#/components/schemas/Chart.Charts.Details"
chartsId:
format: uuid
title: Chart.Axes.Details
type: string
dateCreated:
format: date-time
title: Chart.Axes.Details
type: string
dateModified:
format: date-time
title: Chart.Axes.Details
type: string
id:
format: uuid
title: Chart.Axes.Details
type: string
ooxmlId:
format: int32
title: Chart.Axes.Details
type: integer
titleTextContainer:
$ref: "#/components/schemas/Shared.TextContainer.Details"
userCreated:
format: uuid
title: Chart.Axes.Details
type: string
userModified:
format: uuid
title: Chart.Axes.Details
type: string
type: object
Chart.AxisDataTypes:
properties:
description:
nullable: true
title: Chart.AxisDataTypes
type: string
id:
format: uuid
title: Chart.AxisDataTypes
type: string
name:
nullable: true
title: Chart.AxisDataTypes
type: string
ooxmlName:
nullable: true
title: Chart.AxisDataTypes
type: string
typeId:
format: int32
title: Chart.AxisDataTypes
type: integer
type: object
Chart.ChartData:
properties:
chartId:
format: uuid
nullable: true
title: Chart.ChartData
type: string
id:
format: uuid
title: Chart.ChartData
type: string
type: object
Chart.ChartData.Details:
properties:
chart:
$ref: "#/components/schemas/Chart.Charts.Details"
chartId:
format: uuid
nullable: true
title: Chart.ChartData.Details
type: string
columnCollection:
$ref: "#/components/schemas/Chart.ColumnCollections.Details"
dataPoints:
items:
$ref: "#/components/schemas/Chart.DataPoints.Details"
nullable: true
title: Chart.ChartData.Details
type: array
dateCreated:
format: date-time
title: Chart.ChartData.Details
type: string
dateModified:
format: date-time
title: Chart.ChartData.Details
type: string
id:
format: uuid
title: Chart.ChartData.Details
type: string
rowCollection:
$ref: "#/components/schemas/Chart.RowCollections.Details"
userCreated:
format: uuid
title: Chart.ChartData.Details
type: string
userModified:
format: uuid
title: Chart.ChartData.Details
type: string
type: object
Chart.ChartDataDTO:
properties:
categoryNames:
items:
type: string
nullable: true
type: array
chartId:
format: uuid
type: string
dataPoints:
items:
items:
format: double
type: number
type: array
nullable: true
type: array
seriesNames:
items:
type: string
nullable: true
type: array
type: object
Chart.Charts:
properties:
baseElementBlobUrl:
nullable: true
title: Chart.Charts
type: string
changedBaseElementBlobUrl:
nullable: true
title: Chart.Charts
type: string
id:
format: uuid
title: Chart.Charts
type: string
name:
nullable: true
title: Chart.Charts
type: string
packageUri:
nullable: true
title: Chart.Charts
type: string
parentGraphicId:
format: uuid
nullable: true
title: Chart.Charts
type: string
svgBlobUrl:
nullable: true
title: Chart.Charts
type: string
type: object
Chart.Charts.Details:
properties:
axes:
items:
$ref: "#/components/schemas/Chart.Axes.Details"
nullable: true
title: Chart.Charts.Details
type: array
baseElementBlobUrl:
nullable: true
title: Chart.Charts.Details
type: string
changedBaseElementBlobUrl:
nullable: true
title: Chart.Charts.Details
type: string
chartData:
$ref: "#/components/schemas/Chart.ChartData.Details"
dateCreated:
format: date-time
title: Chart.Charts.Details
type: string
dateModified:
format: date-time
title: Chart.Charts.Details
type: string
id:
format: uuid
title: Chart.Charts.Details
type: string
name:
nullable: true
title: Chart.Charts.Details
type: string
packageUri:
nullable: true
title: Chart.Charts.Details
type: string
parentGraphic:
$ref: "#/components/schemas/Slide.Graphics.Details"
parentGraphicId:
format: uuid
nullable: true
title: Chart.Charts.Details
type: string
svgBlobUrl:
nullable: true
title: Chart.Charts.Details
type: string
titleTextContainer:
$ref: "#/components/schemas/Shared.TextContainer.Details"
userCreated:
format: uuid
title: Chart.Charts.Details
type: string
userModified:
format: uuid
title: Chart.Charts.Details
type: string
type: object
Chart.ColumnCollections:
properties:
chartDataId:
format: uuid
nullable: true
title: Chart.ColumnCollections
type: string
id:
format: uuid
title: Chart.ColumnCollections
type: string
type: object
Chart.ColumnCollections.Details:
properties:
chartData:
$ref: "#/components/schemas/Chart.ChartData.Details"
chartDataId:
format: uuid
nullable: true
title: Chart.ColumnCollections.Details
type: string
columns:
items:
$ref: "#/components/schemas/Chart.Columns.Details"
nullable: true
title: Chart.ColumnCollections.Details
type: array
dateCreated:
format: date-time
title: Chart.ColumnCollections.Details
type: string
dateModified:
format: date-time
title: Chart.ColumnCollections.Details
type: string
id:
format: uuid
title: Chart.ColumnCollections.Details
type: string
userCreated:
format: uuid
title: Chart.ColumnCollections.Details
type: string
userModified:
format: uuid
title: Chart.ColumnCollections.Details
type: string
type: object
Chart.Columns:
properties:
axisId:
format: uuid
nullable: true
title: Chart.Columns
type: string
columnCollectionId:
format: uuid
nullable: true
title: Chart.Columns
type: string
id:
format: uuid
title: Chart.Columns
type: string
index:
format: int32
title: Chart.Columns
type: integer
name:
nullable: true
title: Chart.Columns
type: string
type: object
Chart.Columns.Details:
properties:
axis:
$ref: "#/components/schemas/Chart.Axes.Details"
axisId:
format: uuid
nullable: true
title: Chart.Columns.Details
type: string
columnCollection:
$ref: "#/components/schemas/Chart.ColumnCollections.Details"
columnCollectionId:
format: uuid
nullable: true
title: Chart.Columns.Details
type: string
dateCreated:
format: date-time
title: Chart.Columns.Details
type: string
dateModified:
format: date-time
title: Chart.Columns.Details
type: string
id:
format: uuid
title: Chart.Columns.Details
type: string
index:
format: int32
title: Chart.Columns.Details
type: integer
name:
nullable: true
title: Chart.Columns.Details
type: string
userCreated:
format: uuid
title: Chart.Columns.Details
type: string
userModified:
format: uuid
title: Chart.Columns.Details
type: string
type: object
Chart.DataPoints:
properties:
chartDataId:
format: uuid
nullable: true
title: Chart.DataPoints
type: string
columnId:
format: uuid
nullable: true
title: Chart.DataPoints
type: string
id:
format: uuid
title: Chart.DataPoints
type: string
rowId:
format: uuid
nullable: true
title: Chart.DataPoints
type: string
value:
format: double
title: Chart.DataPoints
type: number
type: object
Chart.DataPoints.Details:
properties:
chartData:
$ref: "#/components/schemas/Chart.ChartData.Details"
chartDataId:
format: uuid
nullable: true
title: Chart.DataPoints.Details
type: string
column:
$ref: "#/components/schemas/Chart.Columns.Details"
columnId:
format: uuid
nullable: true
title: Chart.DataPoints.Details
type: string
dateCreated:
format: date-time
title: Chart.DataPoints.Details
type: string
dateModified:
format: date-time
title: Chart.DataPoints.Details
type: string
id:
format: uuid
title: Chart.DataPoints.Details
type: string
row:
$ref: "#/components/schemas/Chart.Rows.Details"
rowId:
format: uuid
nullable: true
title: Chart.DataPoints.Details
type: string
userCreated:
format: uuid
title: Chart.DataPoints.Details
type: string
userModified:
format: uuid
title: Chart.DataPoints.Details
type: string
value:
format: double
title: Chart.DataPoints.Details
type: number
type: object
Chart.PlotType:
properties:
id:
format: uuid
title: Chart.PlotType
type: string
plotQualifedAssy:
nullable: true
title: Chart.PlotType
type: string
plotTypeName:
nullable: true
title: Chart.PlotType
type: string
rowColTypeId:
format: int32
title: Chart.PlotType
type: integer
typeId:
format: int32
title: Chart.PlotType
type: integer
type: object
Chart.RowCol:
properties:
colName:
nullable: true
title: Chart.RowCol
type: string
colQualifiedAssy:
nullable: true
title: Chart.RowCol
type: string
id:
format: uuid
title: Chart.RowCol
type: string
rowName:
nullable: true
title: Chart.RowCol
type: string
rowQualifedAssy:
nullable: true
title: Chart.RowCol
type: string
typeId:
format: int32
title: Chart.RowCol
type: integer
type: object
Chart.RowCollections:
properties:
axisId:
format: uuid
nullable: true
title: Chart.RowCollections
type: string
chartDataId:
format: uuid
nullable: true
title: Chart.RowCollections
type: string
id:
format: uuid
title: Chart.RowCollections
type: string
nameFormatType:
format: int32
title: Chart.RowCollections
type: integer
type: object
Chart.RowCollections.Details:
properties:
axis:
$ref: "#/components/schemas/Chart.Axes.Details"
axisId:
format: uuid
nullable: true
title: Chart.RowCollections.Details
type: string
chartData:
$ref: "#/components/schemas/Chart.ChartData.Details"
chartDataId:
format: uuid
nullable: true
title: Chart.RowCollections.Details
type: string
dateCreated:
format: date-time
title: Chart.RowCollections.Details
type: string
dateModified:
format: date-time
title: Chart.RowCollections.Details
type: string
id:
format: uuid
title: Chart.RowCollections.Details
type: string
nameFormatType:
format: int32
title: Chart.RowCollections.Details
type: integer
rows:
items:
$ref: "#/components/schemas/Chart.Rows.Details"
nullable: true
title: Chart.RowCollections.Details
type: array
userCreated:
format: uuid
title: Chart.RowCollections.Details
type: string
userModified:
format: uuid
title: Chart.RowCollections.Details
type: string
type: object
Chart.RowNameFormatTypes:
properties:
formatCode:
nullable: true
title: Chart.RowNameFormatTypes
type: string
id:
format: uuid
title: Chart.RowNameFormatTypes
type: string
powerToolsId:
format: int32
title: Chart.RowNameFormatTypes
type: integer
typeId:
format: int32
title: Chart.RowNameFormatTypes
type: integer
type: object
Chart.Rows:
properties:
id:
format: uuid
title: Chart.Rows
type: string
index:
format: int32
title: Chart.Rows
type: integer
name:
nullable: true
title: Chart.Rows
type: string
rowNameCollectionId:
format: uuid
nullable: true
title: Chart.Rows
type: string
type: object
Chart.Rows.Details:
properties:
dateCreated:
format: date-time
title: Chart.Rows.Details
type: string
dateModified:
format: date-time
title: Chart.Rows.Details
type: string
id:
format: uuid
title: Chart.Rows.Details
type: string
index:
format: int32
title: Chart.Rows.Details
type: integer
name:
nullable: true
title: Chart.Rows.Details
type: string
rowNameCollection:
$ref: "#/components/schemas/Chart.RowCollections.Details"
rowNameCollectionId:
format: uuid
nullable: true
title: Chart.Rows.Details
type: string
userCreated:
format: uuid
title: Chart.Rows.Details
type: string
userModified:
format: uuid
title: Chart.Rows.Details
type: string
type: object
ChildObjects:
properties:
entityId:
format: uuid
nullable: true
type: string
entityName:
nullable: true
type: string
objectType:
nullable: true
type: string
parentEntityId:
format: uuid
nullable: true
type: string
parentObjectType:
nullable: true
type: string
type: object
Document:
properties:
baseElementBlobUrl:
nullable: true
title: Document
type: string
blobLocation:
nullable: true
title: Document
type: string
changedBaseElementBlobUrl:
nullable: true
title: Document
type: string
documentTypeId:
format: int32
title: Document
type: integer
filename:
nullable: true
title: Document
type: string
id:
format: uuid
title: Document
type: string
name:
nullable: true
title: Document
type: string
ownerGuid:
format: uuid
title: Document
type: string
packageUri:
nullable: true
title: Document
type: string
storyId:
format: uuid
title: Document
type: string
tableStylesXmlBlobUrl:
nullable: true
title: Document
type: string
title:
nullable: true
title: Document
type: string
type: object
Document.Details:
properties:
baseElementBlobUrl:
nullable: true
title: Document.Details
type: string
blobLocation:
nullable: true
title: Document.Details
type: string
changedBaseElementBlobUrl:
nullable: true
title: Document.Details
type: string
dateCreated:
format: date-time
title: Document.Details
type: string
dateModified:
format: date-time
title: Document.Details
type: string
documentTypeId:
format: int32
title: Document.Details
type: integer
filename:
nullable: true
title: Document.Details
type: string
id:
format: uuid
title: Document.Details
type: string
name:
nullable: true
title: Document.Details
type: string
ownerGuid:
format: uuid
title: Document.Details
type: string
packageUri:
nullable: true
title: Document.Details
type: string
slides:
items:
$ref: "#/components/schemas/Slide.Slides.Details"
nullable: true
title: Document.Details
type: array
storyId:
format: uuid
title: Document.Details
type: string
tableStylesXmlBlobUrl:
nullable: true
title: Document.Details
type: string
title:
nullable: true
title: Document.Details
type: string
userCreated:
format: uuid
title: Document.Details
type: string
userModified:
format: uuid
title: Document.Details
type: string
type: object
DocumentCloneDTO:
properties:
id:
format: uuid
title: DocumentCloneDTO
type: string
storyId:
format: uuid
title: DocumentCloneDTO
type: string
type: object
DocumentType:
properties:
description:
nullable: true
title: DocumentType
type: string
fileExtension:
nullable: true
title: DocumentType
type: string
id:
format: uuid
title: DocumentType
type: string
mimeType:
nullable: true
title: DocumentType
type: string
name:
nullable: true
title: DocumentType
type: string
ooxmlPackageType:
nullable: true
title: DocumentType
type: string
typeId:
format: int32
title: DocumentType
type: integer
type: object
OoxmlDTO:
properties:
id:
format: uuid
nullable: true
type: string
openOfficeXml:
nullable: true
type: string
type:
nullable: true
type: string
type: object
ProblemDetails:
additionalProperties:
type: object
properties:
detail:
nullable: true
type: string
instance:
nullable: true
type: string
status:
format: int32
nullable: true
type: integer
title:
nullable: true
type: string
type:
nullable: true
type: string
type: object
Shared.ColorTransformationAttributes:
properties:
colorTransformationsId:
format: uuid
nullable: true
title: Shared.ColorTransformationAttributes
type: string
id:
format: uuid
title: Shared.ColorTransformationAttributes
type: string
name:
nullable: true
title: Shared.ColorTransformationAttributes
type: string
value:
nullable: true
title: Shared.ColorTransformationAttributes
type: string
type: object
Shared.ColorTransformationAttributes.Details:
properties:
colorTransformation:
$ref: "#/components/schemas/Shared.ColorTransformations.Details"
colorTransformationsId:
format: uuid
nullable: true
title: Shared.ColorTransformationAttributes.Details
type: string
dateCreated:
format: date-time
title: Shared.ColorTransformationAttributes.Details
type: string
dateModified:
format: date-time
title: Shared.ColorTransformationAttributes.Details
type: string
id:
format: uuid
title: Shared.ColorTransformationAttributes.Details
type: string
name:
nullable: true
title: Shared.ColorTransformationAttributes.Details
type: string
userCreated:
format: uuid
title: Shared.ColorTransformationAttributes.Details
type: string
userModified:
format: uuid
title: Shared.ColorTransformationAttributes.Details
type: string
value:
nullable: true
title: Shared.ColorTransformationAttributes.Details
type: string
type: object
Shared.ColorTransformations:
properties:
id:
format: uuid
title: Shared.ColorTransformations
type: string
name:
nullable: true
title: Shared.ColorTransformations
type: string
solidFillsId:
format: uuid
nullable: true
title: Shared.ColorTransformations
type: string
type: object
Shared.ColorTransformations.Details:
properties:
colorTransformationAttributes:
items:
$ref: "#/components/schemas/Shared.ColorTransformationAttributes.Details"
nullable: true
title: Shared.ColorTransformations.Details
type: array
dateCreated:
format: date-time
title: Shared.ColorTransformations.Details
type: string
dateModified:
format: date-time
title: Shared.ColorTransformations.Details
type: string
id:
format: uuid
title: Shared.ColorTransformations.Details
type: string
name:
nullable: true
title: Shared.ColorTransformations.Details
type: string
parentSolidFill:
$ref: "#/components/schemas/Shared.SolidFills.Details"
solidFillsId:
format: uuid
nullable: true
title: Shared.ColorTransformations.Details
type: string
userCreated:
format: uuid
title: Shared.ColorTransformations.Details
type: string
userModified:
format: uuid
title: Shared.ColorTransformations.Details
type: string
type: object
Shared.ColorTypes:
properties:
colorSchemeIndexValueEnum:
format: int32
nullable: true
title: Shared.ColorTypes
type: integer
description:
nullable: true
title: Shared.ColorTypes
type: string
id:
format: uuid
title: Shared.ColorTypes
type: string
name:
nullable: true
title: Shared.ColorTypes
type: string
typeId:
format: int32
title: Shared.ColorTypes
type: integer
type: object
Shared.DashTypes:
properties:
description:
nullable: true
title: Shared.DashTypes
type: string
id:
format: uuid
title: Shared.DashTypes
type: string
name:
nullable: true
title: Shared.DashTypes
type: string
serializedAs:
nullable: true
title: Shared.DashTypes
type: string
typeId:
format: int32
title: Shared.DashTypes
type: integer
type: object
Shared.EffectAttributes:
properties:
attributesJson:
nullable: true
title: Shared.EffectAttributes
type: string
effectId:
format: uuid
nullable: true
title: Shared.EffectAttributes
type: string
effectTypeId:
format: int32
title: Shared.EffectAttributes
type: integer
id:
format: uuid
title: Shared.EffectAttributes
type: string
type: object
Shared.EffectAttributes.Details:
properties:
attributesJson:
nullable: true
title: Shared.EffectAttributes.Details
type: string
dateCreated:
format: date-time
title: Shared.EffectAttributes.Details
type: string
dateModified:
format: date-time
title: Shared.EffectAttributes.Details
type: string
effect:
$ref: "#/components/schemas/Shared.Effects.Details"
effectId:
format: uuid
nullable: true
title: Shared.EffectAttributes.Details
type: string
effectTypeId:
format: int32
title: Shared.EffectAttributes.Details
type: integer
fillMap:
$ref: "#/components/schemas/Shared.FillMap.Details"
id:
format: uuid
title: Shared.EffectAttributes.Details
type: string
userCreated:
format: uuid
title: Shared.EffectAttributes.Details
type: string
userModified:
format: uuid
title: Shared.EffectAttributes.Details
type: string
type: object
Shared.EffectTypes:
properties:
description:
nullable: true
title: Shared.EffectTypes
type: string
id:
format: uuid
title: Shared.EffectTypes
type: string
name:
nullable: true
title: Shared.EffectTypes
type: string
typeId:
format: int32
title: Shared.EffectTypes
type: integer
type: object
Shared.Effects:
properties:
connectorId:
format: uuid
nullable: true
title: Shared.Effects
type: string
effectMapId:
format: uuid
nullable: true
title: Shared.Effects
type: string
id:
format: uuid
title: Shared.Effects
type: string
name:
nullable: true
title: Shared.Effects
type: string
shapeId:
format: uuid
nullable: true
title: Shared.Effects
type: string
type: object
Shared.Effects.Details:
properties:
connectorId:
format: uuid
nullable: true
title: Shared.Effects.Details
type: string
dateCreated:
format: date-time
title: Shared.Effects.Details
type: string
dateModified:
format: date-time
title: Shared.Effects.Details
type: string
effectAttributes:
items:
$ref: "#/components/schemas/Shared.EffectAttributes.Details"
nullable: true
title: Shared.Effects.Details
type: array
effectMap:
$ref: "#/components/schemas/Theme.EffectMap.Details"
effectMapId:
format: uuid
nullable: true
title: Shared.Effects.Details
type: string
id:
format: uuid
title: Shared.Effects.Details
type: string
name:
nullable: true
title: Shared.Effects.Details
type: string
parentConnector:
$ref: "#/components/schemas/Slide.Connector.Details"
parentShape:
$ref: "#/components/schemas/Slide.Shapes.Details"
shapeId:
format: uuid
nullable: true
title: Shared.Effects.Details
type: string
userCreated:
format: uuid
title: Shared.Effects.Details
type: string
userModified:
format: uuid
title: Shared.Effects.Details
type: string
type: object
Shared.FillMap:
properties:
connectorId:
format: uuid
nullable: true
title: Shared.FillMap
type: string
effectAttributeId:
format: uuid
nullable: true
title: Shared.FillMap
type: string
fillTypeId:
format: int32
title: Shared.FillMap
type: integer
id:
format: uuid
title: Shared.FillMap
type: string
shapeId:
format: uuid
nullable: true
title: Shared.FillMap
type: string
tableCellId:
format: uuid
nullable: true
title: Shared.FillMap
type: string
themeBackgroundFillId:
format: uuid
nullable: true
title: Shared.FillMap
type: string
themeFillId:
format: uuid
nullable: true
title: Shared.FillMap
type: string
type: object
Shared.FillMap.Details:
properties:
connector:
$ref: "#/components/schemas/Slide.Connector.Details"
connectorId:
format: uuid
nullable: true
title: Shared.FillMap.Details
type: string
dateCreated:
format: date-time
title: Shared.FillMap.Details
type: string
dateModified:
format: date-time
title: Shared.FillMap.Details
type: string
effectAttribute:
$ref: "#/components/schemas/Shared.EffectAttributes.Details"
effectAttributeId:
format: uuid
nullable: true
title: Shared.FillMap.Details
type: string
fillTypeId:
format: int32
title: Shared.FillMap.Details
type: integer
gradientFill:
$ref: "#/components/schemas/Shared.GradientFills.Details"
id:
format: uuid
title: Shared.FillMap.Details
type: string
imageFill:
$ref: "#/components/schemas/Shared.ImageFills.Details"
shape:
$ref: "#/components/schemas/Slide.Shapes.Details"
shapeId:
format: uuid
nullable: true
title: Shared.FillMap.Details
type: string
solidFill:
$ref: "#/components/schemas/Shared.SolidFills.Details"
tableCell:
$ref: "#/components/schemas/Table.Cells.Details"
tableCellId:
format: uuid
nullable: true
title: Shared.FillMap.Details
type: string
themeBackgroundFill:
$ref: "#/components/schemas/Theme.BackgroundFills.Details"
themeBackgroundFillId:
format: uuid
nullable: true
title: Shared.FillMap.Details
type: string
themeFill:
$ref: "#/components/schemas/Theme.Fills.Details"
themeFillId:
format: uuid
nullable: true
title: Shared.FillMap.Details
type: string
userCreated:
format: uuid
title: Shared.FillMap.Details
type: string
userModified:
format: uuid
title: Shared.FillMap.Details
type: string
type: object
Shared.FillTypes:
properties:
description:
nullable: true
title: Shared.FillTypes
type: string
id:
format: uuid
title: Shared.FillTypes
type: string
name:
nullable: true
title: Shared.FillTypes
type: string
typeId:
format: int32
title: Shared.FillTypes
type: integer
type: object
Shared.GradientFills:
properties:
angle:
format: int32
nullable: true
title: Shared.GradientFills
type: integer
fillMapId:
format: uuid
nullable: true
title: Shared.GradientFills
type: string
id:
format: uuid
title: Shared.GradientFills
type: string
isPath:
title: Shared.GradientFills
type: boolean
pathType:
nullable: true
title: Shared.GradientFills
type: string
rotateWithShape:
title: Shared.GradientFills
type: boolean
type: object
Shared.GradientFills.Details:
properties:
angle:
format: int32
nullable: true
title: Shared.GradientFills.Details
type: integer
dateCreated:
format: date-time
title: Shared.GradientFills.Details
type: string
dateModified:
format: date-time
title: Shared.GradientFills.Details
type: string
fillMap:
$ref: "#/components/schemas/Shared.FillMap.Details"
fillMapId:
format: uuid
nullable: true
title: Shared.GradientFills.Details
type: string
gradientStops:
items:
$ref: "#/components/schemas/Shared.GradientStops.Details"
nullable: true
title: Shared.GradientFills.Details
type: array
id:
format: uuid
title: Shared.GradientFills.Details
type: string
isPath:
title: Shared.GradientFills.Details
type: boolean
pathType:
nullable: true
title: Shared.GradientFills.Details
type: string
rotateWithShape:
title: Shared.GradientFills.Details
type: boolean
userCreated:
format: uuid
title: Shared.GradientFills.Details
type: string
userModified:
format: uuid
title: Shared.GradientFills.Details
type: string
type: object
Shared.GradientStops:
properties:
gradientFillsId:
format: uuid
nullable: true
title: Shared.GradientStops
type: string
id:
format: uuid
title: Shared.GradientStops
type: string
position:
format: int32
title: Shared.GradientStops
type: integer
type: object
Shared.GradientStops.Details:
properties:
dateCreated:
format: date-time
title: Shared.GradientStops.Details
type: string
dateModified:
format: date-time
title: Shared.GradientStops.Details
type: string
gradientFill:
$ref: "#/components/schemas/Shared.GradientFills.Details"
gradientFillsId:
format: uuid
nullable: true
title: Shared.GradientStops.Details
type: string
id:
format: uuid
title: Shared.GradientStops.Details
type: string
position:
format: int32
title: Shared.GradientStops.Details
type: integer
solidFill:
$ref: "#/components/schemas/Shared.SolidFills.Details"
userCreated:
format: uuid
title: Shared.GradientStops.Details
type: string
userModified:
format: uuid
title: Shared.GradientStops.Details
type: string
type: object
Shared.ImageFills:
properties:
compressionState:
nullable: true
title: Shared.ImageFills
type: string
dpi:
format: int32
nullable: true
title: Shared.ImageFills
type: integer
effectsJson:
nullable: true
title: Shared.ImageFills
type: string
fillMapId:
format: uuid
nullable: true
title: Shared.ImageFills
type: string
id:
format: uuid
title: Shared.ImageFills
type: string
rotateWithShape:
title: Shared.ImageFills
type: boolean
sourceRectangle:
nullable: true
title: Shared.ImageFills
type: string
stretch:
title: Shared.ImageFills
type: boolean
tile:
nullable: true
title: Shared.ImageFills
type: string
type: object
Shared.ImageFills.Details:
properties:
compressionState:
nullable: true
title: Shared.ImageFills.Details
type: string
dateCreated:
format: date-time
title: Shared.ImageFills.Details
type: string
dateModified:
format: date-time
title: Shared.ImageFills.Details
type: string
dpi:
format: int32
nullable: true
title: Shared.ImageFills.Details
type: integer
effectsJson:
nullable: true
title: Shared.ImageFills.Details
type: string
fillMap:
$ref: "#/components/schemas/Shared.FillMap.Details"
fillMapId:
format: uuid
nullable: true
title: Shared.ImageFills.Details
type: string
id:
format: uuid
title: Shared.ImageFills.Details
type: string
picture:
$ref: "#/components/schemas/Shared.Pictures.Details"
rotateWithShape:
title: Shared.ImageFills.Details
type: boolean
sourceRectangle:
nullable: true
title: Shared.ImageFills.Details
type: string
stretch:
title: Shared.ImageFills.Details
type: boolean
tile:
nullable: true
title: Shared.ImageFills.Details
type: string
userCreated:
format: uuid
title: Shared.ImageFills.Details
type: string
userModified:
format: uuid
title: Shared.ImageFills.Details
type: string
type: object
Shared.LineEndSizes:
properties:
description:
nullable: true
title: Shared.LineEndSizes
type: string
id:
format: uuid
title: Shared.LineEndSizes
type: string
name:
nullable: true
title: Shared.LineEndSizes
type: string
serializedAs:
nullable: true
title: Shared.LineEndSizes
type: string
typeId:
format: int32
title: Shared.LineEndSizes
type: integer
type: object
Shared.LineEndTypes:
properties:
description:
nullable: true
title: Shared.LineEndTypes
type: string
id:
format: uuid
title: Shared.LineEndTypes
type: string
name:
nullable: true
title: Shared.LineEndTypes
type: string
serializedAs:
nullable: true
title: Shared.LineEndTypes
type: string
typeId:
format: int32
title: Shared.LineEndTypes
type: integer
type: object
Shared.Lines:
properties:
bLtoTRBorderId:
format: uuid
nullable: true
title: Shared.Lines
type: string
bottomBorderId:
format: uuid
nullable: true
title: Shared.Lines
type: string
connectorId:
format: uuid
nullable: true
title: Shared.Lines
type: string
dashTypeId:
format: int32
title: Shared.Lines
type: integer
headEndHeightId:
format: int32
title: Shared.Lines
type: integer
headEndTypeId:
format: int32
title: Shared.Lines
type: integer
headEndWidthId:
format: int32
title: Shared.Lines
type: integer
id:
format: uuid
title: Shared.Lines
type: string
leftBorderId:
format: uuid
nullable: true
title: Shared.Lines
type: string
lineMapId:
format: uuid
nullable: true
title: Shared.Lines
type: string
rightBorderId:
format: uuid
nullable: true
title: Shared.Lines
type: string
shapeId:
format: uuid
nullable: true
title: Shared.Lines
type: string
tLtoBRBorderId:
format: uuid
nullable: true
title: Shared.Lines
type: string
tailEndHeightId:
format: int32
title: Shared.Lines
type: integer
tailEndTypeId:
format: int32
title: Shared.Lines
type: integer
tailEndWidthId:
format: int32
title: Shared.Lines
type: integer
topBorderId:
format: uuid
nullable: true
title: Shared.Lines
type: string
weight:
format: int32
title: Shared.Lines
type: integer
type: object
Shared.Lines.Details:
properties:
bLtoTRBorder:
$ref: "#/components/schemas/Table.Borders.Details"
bLtoTRBorderId:
format: uuid
nullable: true
title: Shared.Lines.Details
type: string
bottomBorder:
$ref: "#/components/schemas/Table.Borders.Details"
bottomBorderId:
format: uuid
nullable: true
title: Shared.Lines.Details
type: string
connectorId:
format: uuid
nullable: true
title: Shared.Lines.Details
type: string
dashTypeId:
format: int32
title: Shared.Lines.Details
type: integer
dateCreated:
format: date-time
title: Shared.Lines.Details
type: string
dateModified:
format: date-time
title: Shared.Lines.Details
type: string
headEndHeightId:
format: int32
title: Shared.Lines.Details
type: integer
headEndTypeId:
format: int32
title: Shared.Lines.Details
type: integer
headEndWidthId:
format: int32
title: Shared.Lines.Details
type: integer
id:
format: uuid
title: Shared.Lines.Details
type: string
leftBorder:
$ref: "#/components/schemas/Table.Borders.Details"
leftBorderId:
format: uuid
nullable: true
title: Shared.Lines.Details
type: string
lineColorSolidFill:
$ref: "#/components/schemas/Shared.SolidFills.Details"
lineMap:
$ref: "#/components/schemas/Theme.LineMap.Details"
lineMapId:
format: uuid
nullable: true
title: Shared.Lines.Details
type: string
parentConnector:
$ref: "#/components/schemas/Slide.Connector.Details"
parentShape:
$ref: "#/components/schemas/Slide.Shapes.Details"
rightBorder:
$ref: "#/components/schemas/Table.Borders.Details"
rightBorderId:
format: uuid
nullable: true
title: Shared.Lines.Details
type: string
shapeId:
format: uuid
nullable: true
title: Shared.Lines.Details
type: string
tLtoBRBorder:
$ref: "#/components/schemas/Table.Borders.Details"
tLtoBRBorderId:
format: uuid
nullable: true
title: Shared.Lines.Details
type: string
tailEndHeightId:
format: int32
title: Shared.Lines.Details
type: integer
tailEndTypeId:
format: int32
title: Shared.Lines.Details
type: integer
tailEndWidthId:
format: int32
title: Shared.Lines.Details
type: integer
topBorder:
$ref: "#/components/schemas/Table.Borders.Details"
topBorderId:
format: uuid
nullable: true
title: Shared.Lines.Details
type: string
userCreated:
format: uuid
title: Shared.Lines.Details
type: string
userModified:
format: uuid
title: Shared.Lines.Details
type: string
weight:
format: int32
title: Shared.Lines.Details
type: integer
type: object
Shared.Paragraph:
properties:
id:
format: uuid
title: Shared.Paragraph
type: string
number:
format: int32
title: Shared.Paragraph
type: integer
textContainerId:
format: uuid
nullable: true
title: Shared.Paragraph
type: string
type: object
Shared.Paragraph.Details:
properties:
dateCreated:
format: date-time
title: Shared.Paragraph.Details
type: string
dateModified:
format: date-time
title: Shared.Paragraph.Details
type: string
id:
format: uuid
title: Shared.Paragraph.Details
type: string
number:
format: int32
title: Shared.Paragraph.Details
type: integer
text:
items:
$ref: "#/components/schemas/Shared.Text.Details"
nullable: true
title: Shared.Paragraph.Details
type: array
textContainer:
$ref: "#/components/schemas/Shared.TextContainer.Details"
textContainerId:
format: uuid
nullable: true
title: Shared.Paragraph.Details
type: string
userCreated:
format: uuid
title: Shared.Paragraph.Details
type: string
userModified:
format: uuid
title: Shared.Paragraph.Details
type: string
type: object
Shared.Pictures:
properties:
baseElementBlobUrl:
nullable: true
title: Shared.Pictures
type: string
changedBaseElementBlobUrl:
nullable: true
title: Shared.Pictures
type: string
fileExtension:
nullable: true
title: Shared.Pictures
type: string
graphicsId:
format: uuid
nullable: true
title: Shared.Pictures
type: string
id:
format: uuid
title: Shared.Pictures
type: string
imageFileBlobUrl:
nullable: true
title: Shared.Pictures
type: string
imageFillsId:
format: uuid
nullable: true
title: Shared.Pictures
type: string
name:
nullable: true
title: Shared.Pictures
type: string
packageUri:
nullable: true
title: Shared.Pictures
type: string
type: object
Shared.Pictures.Details:
properties:
baseElementBlobUrl:
nullable: true
title: Shared.Pictures.Details
type: string
changedBaseElementBlobUrl:
nullable: true
title: Shared.Pictures.Details
type: string
dateCreated:
format: date-time
title: Shared.Pictures.Details
type: string
dateModified:
format: date-time
title: Shared.Pictures.Details
type: string
fileExtension:
nullable: true
title: Shared.Pictures.Details
type: string
graphicsId:
format: uuid
nullable: true
title: Shared.Pictures.Details
type: string
id:
format: uuid
title: Shared.Pictures.Details
type: string
imageFileBlobUrl:
nullable: true
title: Shared.Pictures.Details
type: string
imageFill:
$ref: "#/components/schemas/Shared.ImageFills.Details"
imageFillsId:
format: uuid
nullable: true
title: Shared.Pictures.Details
type: string
name:
nullable: true
title: Shared.Pictures.Details
type: string
packageUri:
nullable: true
title: Shared.Pictures.Details
type: string
parentGraphic:
$ref: "#/components/schemas/Slide.Graphics.Details"
userCreated:
format: uuid
title: Shared.Pictures.Details
type: string
userModified:
format: uuid
title: Shared.Pictures.Details
type: string
type: object
Shared.SolidFills:
properties:
colorTypeId:
format: int32
nullable: true
title: Shared.SolidFills
type: integer
fillMapId:
format: uuid
nullable: true
title: Shared.SolidFills
type: string
hexValue:
nullable: true
title: Shared.SolidFills
type: string
id:
format: uuid
title: Shared.SolidFills
type: string
isUserColor:
title: Shared.SolidFills
type: boolean
parentGradientStopId:
format: uuid
nullable: true
title: Shared.SolidFills
type: string
parentLineId:
format: uuid
nullable: true
title: Shared.SolidFills
type: string
parentTextId:
format: uuid
nullable: true
title: Shared.SolidFills
type: string
type: object
Shared.SolidFills.Details:
properties:
colorTransformations:
$ref: "#/components/schemas/Shared.ColorTransformations.Details"
colorTypeId:
format: int32
nullable: true
title: Shared.SolidFills.Details
type: integer
dateCreated:
format: date-time
title: Shared.SolidFills.Details
type: string
dateModified:
format: date-time
title: Shared.SolidFills.Details
type: string
fillMapId:
format: uuid
nullable: true
title: Shared.SolidFills.Details
type: string
hexValue:
nullable: true
title: Shared.SolidFills.Details
type: string
id:
format: uuid
title: Shared.SolidFills.Details
type: string
isUserColor:
title: Shared.SolidFills.Details
type: boolean
parentFillMap:
$ref: "#/components/schemas/Shared.FillMap.Details"
parentGradientStop:
$ref: "#/components/schemas/Shared.GradientStops.Details"
parentGradientStopId:
format: uuid
nullable: true
title: Shared.SolidFills.Details
type: string
parentLine:
$ref: "#/components/schemas/Shared.Lines.Details"
parentLineId:
format: uuid
nullable: true
title: Shared.SolidFills.Details
type: string
parentText:
$ref: "#/components/schemas/Shared.Text.Details"
parentTextId:
format: uuid
nullable: true
title: Shared.SolidFills.Details
type: string
userCreated:
format: uuid
title: Shared.SolidFills.Details
type: string
userModified:
format: uuid
title: Shared.SolidFills.Details
type: string
type: object
Shared.Text:
properties:
colorSolidFillsId:
format: uuid
nullable: true
title: Shared.Text
type: string
font:
nullable: true
title: Shared.Text
type: string
fontSize:
format: int32
nullable: true
title: Shared.Text
type: integer
id:
format: uuid
title: Shared.Text
type: string
isBold:
title: Shared.Text
type: boolean
isItalic:
title: Shared.Text
type: boolean
isThemeFont:
title: Shared.Text
type: boolean
isUnderline:
title: Shared.Text
type: boolean
paragraphId:
format: uuid
nullable: true
title: Shared.Text
type: string
rawText:
nullable: true
title: Shared.Text
type: string
sequence:
format: int32
title: Shared.Text
type: integer
type: object
Shared.Text.Details:
properties:
colorSolidFill:
$ref: "#/components/schemas/Shared.SolidFills.Details"
colorSolidFillsId:
format: uuid
nullable: true
title: Shared.Text.Details
type: string
dateCreated:
format: date-time
title: Shared.Text.Details
type: string
dateModified:
format: date-time
title: Shared.Text.Details
type: string
font:
nullable: true
title: Shared.Text.Details
type: string
fontSize:
format: int32
nullable: true
title: Shared.Text.Details
type: integer
id:
format: uuid
title: Shared.Text.Details
type: string
isBold:
title: Shared.Text.Details
type: boolean
isItalic:
title: Shared.Text.Details
type: boolean
isThemeFont:
title: Shared.Text.Details
type: boolean
isUnderline:
title: Shared.Text.Details
type: boolean
paragraph:
$ref: "#/components/schemas/Shared.Paragraph.Details"
paragraphId:
format: uuid
nullable: true
title: Shared.Text.Details
type: string
rawText:
nullable: true
title: Shared.Text.Details
type: string
sequence:
format: int32
title: Shared.Text.Details
type: integer
userCreated:
format: uuid
title: Shared.Text.Details
type: string
userModified:
format: uuid
title: Shared.Text.Details
type: string
type: object
Shared.TextContainer:
properties:
axisId:
format: uuid
nullable: true
title: Shared.TextContainer
type: string
chartId:
format: uuid
nullable: true
title: Shared.TextContainer
type: string
id:
format: uuid
title: Shared.TextContainer
type: string
outerXml:
nullable: true
title: Shared.TextContainer
type: string
shapeId:
format: uuid
nullable: true
title: Shared.TextContainer
type: string
tableCellId:
format: uuid
nullable: true
title: Shared.TextContainer
type: string
type: object
Shared.TextContainer.Details:
properties:
axis:
$ref: "#/components/schemas/Chart.Axes.Details"
axisId:
format: uuid
nullable: true
title: Shared.TextContainer.Details
type: string
chart:
$ref: "#/components/schemas/Chart.Charts.Details"
chartId:
format: uuid
nullable: true
title: Shared.TextContainer.Details
type: string
dateCreated:
format: date-time
title: Shared.TextContainer.Details
type: string
dateModified:
format: date-time
title: Shared.TextContainer.Details
type: string
id:
format: uuid
title: Shared.TextContainer.Details
type: string
outerXml:
nullable: true
title: Shared.TextContainer.Details
type: string
paragraphs:
items:
$ref: "#/components/schemas/Shared.Paragraph.Details"
nullable: true
title: Shared.TextContainer.Details
type: array
parentShape:
$ref: "#/components/schemas/Slide.Shapes.Details"
shapeId:
format: uuid
nullable: true
title: Shared.TextContainer.Details
type: string
tableCell:
$ref: "#/components/schemas/Table.Cells.Details"
tableCellId:
format: uuid
nullable: true
title: Shared.TextContainer.Details
type: string
userCreated:
format: uuid
title: Shared.TextContainer.Details
type: string
userModified:
format: uuid
title: Shared.TextContainer.Details
type: string
type: object
Slide.ColorMaps:
properties:
accent1:
format: int32
title: Slide.ColorMaps
type: integer
accent2:
format: int32
title: Slide.ColorMaps
type: integer
accent3:
format: int32
title: Slide.ColorMaps
type: integer
accent4:
format: int32
title: Slide.ColorMaps
type: integer
accent5:
format: int32
title: Slide.ColorMaps
type: integer
accent6:
format: int32
title: Slide.ColorMaps
type: integer
background1:
format: int32
title: Slide.ColorMaps
type: integer
background2:
format: int32
title: Slide.ColorMaps
type: integer
followedHyperlink:
format: int32
title: Slide.ColorMaps
type: integer
hyperlink:
format: int32
title: Slide.ColorMaps
type: integer
id:
format: uuid
title: Slide.ColorMaps
type: string
slideMasterId:
format: uuid
nullable: true
title: Slide.ColorMaps
type: string
text1:
format: int32
title: Slide.ColorMaps
type: integer
text2:
format: int32
title: Slide.ColorMaps
type: integer
type: object
Slide.ColorMaps.Details:
properties:
accent1:
format: int32
title: Slide.ColorMaps.Details
type: integer
accent2:
format: int32
title: Slide.ColorMaps.Details
type: integer
accent3:
format: int32
title: Slide.ColorMaps.Details
type: integer
accent4:
format: int32
title: Slide.ColorMaps.Details
type: integer
accent5:
format: int32
title: Slide.ColorMaps.Details
type: integer
accent6:
format: int32
title: Slide.ColorMaps.Details
type: integer
background1:
format: int32
title: Slide.ColorMaps.Details
type: integer
background2:
format: int32
title: Slide.ColorMaps.Details
type: integer
dateCreated:
format: date-time
title: Slide.ColorMaps.Details
type: string
dateModified:
format: date-time
title: Slide.ColorMaps.Details
type: string
followedHyperlink:
format: int32
title: Slide.ColorMaps.Details
type: integer
hyperlink:
format: int32
title: Slide.ColorMaps.Details
type: integer
id:
format: uuid
title: Slide.ColorMaps.Details
type: string
slideMaster:
$ref: "#/components/schemas/Slide.SlideMasters.Details"
slideMasterId:
format: uuid
nullable: true
title: Slide.ColorMaps.Details
type: string
text1:
format: int32
title: Slide.ColorMaps.Details
type: integer
text2:
format: int32
title: Slide.ColorMaps.Details
type: integer
userCreated:
format: uuid
title: Slide.ColorMaps.Details
type: string
userModified:
format: uuid
title: Slide.ColorMaps.Details
type: string
type: object
Slide.Connector:
properties:
baseElementBlobUrl:
nullable: true
title: Slide.Connector
type: string
changedBaseElementBlobUrl:
nullable: true
title: Slide.Connector
type: string
endConnectionIdx:
format: int32
title: Slide.Connector
type: integer
endConnectionShapeId:
format: uuid
nullable: true
title: Slide.Connector
type: string
flipHorizontal:
title: Slide.Connector
type: boolean
flipVertical:
title: Slide.Connector
type: boolean
freeFormPathXml:
nullable: true
title: Slide.Connector
type: string
groupElementsId:
format: uuid
nullable: true
title: Slide.Connector
type: string
hidden:
title: Slide.Connector
type: boolean
id:
format: uuid
title: Slide.Connector
type: string
isThemeEffect:
title: Slide.Connector
type: boolean
isThemeFill:
title: Slide.Connector
type: boolean
isThemeLine:
title: Slide.Connector
type: boolean
name:
nullable: true
title: Slide.Connector
type: string
ooxmlId:
format: int32
title: Slide.Connector
type: integer
packageUri:
nullable: true
title: Slide.Connector
type: string
presetTypeId:
nullable: true
title: Slide.Connector
type: string
rotation:
format: int32
title: Slide.Connector
type: integer
startConnectionIdx:
format: int32
title: Slide.Connector
type: integer
startConnectionShapeId:
format: uuid
nullable: true
title: Slide.Connector
type: string
svgBlobUrl:
nullable: true
title: Slide.Connector
type: string
type: object
Slide.Connector.Details:
properties:
baseElementBlobUrl:
nullable: true
title: Slide.Connector.Details
type: string
changedBaseElementBlobUrl:
nullable: true
title: Slide.Connector.Details
type: string
dateCreated:
format: date-time
title: Slide.Connector.Details
type: string
dateModified:
format: date-time
title: Slide.Connector.Details
type: string
effect:
$ref: "#/components/schemas/Shared.Effects.Details"
endConnectionIdx:
format: int32
title: Slide.Connector.Details
type: integer
endConnectionShape:
$ref: "#/components/schemas/Slide.Shapes.Details"
endConnectionShapeId:
format: uuid
nullable: true
title: Slide.Connector.Details
type: string
fillMap:
$ref: "#/components/schemas/Shared.FillMap.Details"
flipHorizontal:
title: Slide.Connector.Details
type: boolean
flipVertical:
title: Slide.Connector.Details
type: boolean
freeFormPathXml:
nullable: true
title: Slide.Connector.Details
type: string
groupElement:
$ref: "#/components/schemas/Slide.GroupElements.Details"
groupElementsId:
format: uuid
nullable: true
title: Slide.Connector.Details
type: string
hidden:
title: Slide.Connector.Details
type: boolean
id:
format: uuid
title: Slide.Connector.Details
type: string
isThemeEffect:
title: Slide.Connector.Details
type: boolean
isThemeFill:
title: Slide.Connector.Details
type: boolean
isThemeLine:
title: Slide.Connector.Details
type: boolean
line:
$ref: "#/components/schemas/Shared.Lines.Details"
name:
nullable: true
title: Slide.Connector.Details
type: string
ooxmlId:
format: int32
title: Slide.Connector.Details
type: integer
packageUri:
nullable: true
title: Slide.Connector.Details
type: string
presetTypeId:
nullable: true
title: Slide.Connector.Details
type: string
rotation:
format: int32
title: Slide.Connector.Details
type: integer
startConnectionIdx:
format: int32
title: Slide.Connector.Details
type: integer
startConnectionShape:
$ref: "#/components/schemas/Slide.Shapes.Details"
startConnectionShapeId:
format: uuid
nullable: true
title: Slide.Connector.Details
type: string
svgBlobUrl:
nullable: true
title: Slide.Connector.Details
type: string
userCreated:
format: uuid
title: Slide.Connector.Details
type: string
userModified:
format: uuid
title: Slide.Connector.Details
type: string
type: object
Slide.GraphicTypes:
properties:
description:
nullable: true
title: Slide.GraphicTypes
type: string
id:
format: uuid
title: Slide.GraphicTypes
type: string
name:
nullable: true
title: Slide.GraphicTypes
type: string
typeId:
format: int32
title: Slide.GraphicTypes
type: integer
type: object
Slide.Graphics:
properties:
graphicTypeId:
format: int32
title: Slide.Graphics
type: integer
groupElementsId:
format: uuid
nullable: true
title: Slide.Graphics
type: string
height:
format: int32
title: Slide.Graphics
type: integer
id:
format: uuid
title: Slide.Graphics
type: string
name:
nullable: true
title: Slide.Graphics
type: string
ooxmlId:
format: int32
title: Slide.Graphics
type: integer
width:
format: int32
title: Slide.Graphics
type: integer
xOffset:
format: int32
title: Slide.Graphics
type: integer
yOffset:
format: int32
title: Slide.Graphics
type: integer
type: object
Slide.Graphics.Details:
description: "The graphics class provides a bridge between the Slides and \r
lower level models including Charts, Tables, Pictures, and SmartArts"
properties:
chart:
$ref: "#/components/schemas/Chart.Charts.Details"
dateCreated:
format: date-time
title: Slide.Graphics.Details
type: string
dateModified:
format: date-time
title: Slide.Graphics.Details
type: string
graphicTypeId:
format: int32
title: Slide.Graphics.Details
type: integer
groupElement:
$ref: "#/components/schemas/Slide.GroupElements.Details"
groupElementsId:
description: Foreign key to the GroupElements object
format: uuid
nullable: true
title: Slide.Graphics.Details
type: string
height:
format: int32
title: Slide.Graphics.Details
type: integer
id:
format: uuid
title: Slide.Graphics.Details
type: string
name:
nullable: true
title: Slide.Graphics.Details
type: string
ooxmlId:
format: int32
title: Slide.Graphics.Details
type: integer
picture:
$ref: "#/components/schemas/Shared.Pictures.Details"
smartArt:
$ref: "#/components/schemas/Slide.SmartArts.Details"
table:
$ref: "#/components/schemas/Table.Tables.Details"
userCreated:
format: uuid
title: Slide.Graphics.Details
type: string
userModified:
format: uuid
title: Slide.Graphics.Details
type: string
width:
format: int32
title: Slide.Graphics.Details
type: integer
xOffset:
format: int32
title: Slide.Graphics.Details
type: integer
yOffset:
format: int32
title: Slide.Graphics.Details
type: integer
type: object
Slide.GroupElementTypes:
properties:
description:
nullable: true
title: Slide.GroupElementTypes
type: string
id:
format: uuid
title: Slide.GroupElementTypes
type: string
name:
nullable: true
title: Slide.GroupElementTypes
type: string
typeId:
format: int32
title: Slide.GroupElementTypes
type: integer
type: object
Slide.GroupElementTypes.Details:
properties:
dateCreated:
format: date-time
title: Slide.GroupElementTypes.Details
type: string
dateModified:
format: date-time
title: Slide.GroupElementTypes.Details
type: string
description:
nullable: true
title: Slide.GroupElementTypes.Details
type: string
id:
format: uuid
title: Slide.GroupElementTypes.Details
type: string
name:
nullable: true
title: Slide.GroupElementTypes.Details
type: string
typeId:
format: int32
title: Slide.GroupElementTypes.Details
type: integer
userCreated:
format: uuid
title: Slide.GroupElementTypes.Details
type: string
userModified:
format: uuid
title: Slide.GroupElementTypes.Details
type: string
type: object
Slide.GroupElements:
properties:
groupElementTypeId:
format: int32
title: Slide.GroupElements
type: integer
groupElementTypePk:
format: uuid
nullable: true
title: Slide.GroupElements
type: string
id:
format: uuid
title: Slide.GroupElements
type: string
parentGroupElementId:
format: uuid
nullable: true
title: Slide.GroupElements
type: string
shapeTreeId:
format: uuid
nullable: true
title: Slide.GroupElements
type: string
ultimateParentShapeTreeId:
format: uuid
nullable: true
title: Slide.GroupElements
type: string
type: object
Slide.GroupElements.Details:
properties:
childGroupElements:
items:
$ref: "#/components/schemas/Slide.GroupElements.Details"
nullable: true
title: Slide.GroupElements.Details
type: array
connector:
$ref: "#/components/schemas/Slide.Connector.Details"
dateCreated:
format: date-time
title: Slide.GroupElements.Details
type: string
dateModified:
format: date-time
title: Slide.GroupElements.Details
type: string
graphic:
$ref: "#/components/schemas/Slide.Graphics.Details"
group:
$ref: "#/components/schemas/Slide.Groups.Details"
groupElementTypeId:
format: int32
title: Slide.GroupElements.Details
type: integer
groupElementTypePk:
format: uuid
nullable: true
title: Slide.GroupElements.Details
type: string
id:
format: uuid
title: Slide.GroupElements.Details
type: string
parentGroupElement:
$ref: "#/components/schemas/Slide.GroupElements.Details"
parentGroupElementId:
format: uuid
nullable: true
title: Slide.GroupElements.Details
type: string
shape:
$ref: "#/components/schemas/Slide.Shapes.Details"
shapeTree:
$ref: "#/components/schemas/Slide.ShapeTrees.Details"
shapeTreeId:
format: uuid
nullable: true
title: Slide.GroupElements.Details
type: string
typeInfo:
$ref: "#/components/schemas/Slide.GroupElementTypes.Details"
ultimateParentShapeTreeId:
format: uuid
nullable: true
title: Slide.GroupElements.Details
type: string
userCreated:
format: uuid
title: Slide.GroupElements.Details
type: string
userModified:
format: uuid
title: Slide.GroupElements.Details
type: string
type: object
Slide.Groups:
properties:
baseElementBlobUrl:
nullable: true
title: Slide.Groups
type: string
changedBaseElementBlobUrl:
nullable: true
title: Slide.Groups
type: string
groupElementId:
format: uuid
nullable: true
title: Slide.Groups
type: string
hidden:
title: Slide.Groups
type: boolean
id:
format: uuid
title: Slide.Groups
type: string
name:
nullable: true
title: Slide.Groups
type: string
ooxmlId:
format: int32
title: Slide.Groups
type: integer
packageUri:
nullable: true
title: Slide.Groups
type: string
svgBlobUrl:
nullable: true
title: Slide.Groups
type: string
title:
nullable: true
title: Slide.Groups
type: string
type: object
Slide.Groups.Details:
properties:
baseElementBlobUrl:
nullable: true
title: Slide.Groups.Details
type: string
changedBaseElementBlobUrl:
nullable: true
title: Slide.Groups.Details
type: string
dateCreated:
format: date-time
title: Slide.Groups.Details
type: string
dateModified:
format: date-time
title: Slide.Groups.Details
type: string
groupElement:
$ref: "#/components/schemas/Slide.GroupElements.Details"
groupElementId:
format: uuid
nullable: true
title: Slide.Groups.Details
type: string
hidden:
title: Slide.Groups.Details
type: boolean
id:
format: uuid
title: Slide.Groups.Details
type: string
name:
nullable: true
title: Slide.Groups.Details
type: string
ooxmlId:
format: int32
title: Slide.Groups.Details
type: integer
packageUri:
nullable: true
title: Slide.Groups.Details
type: string
svgBlobUrl:
nullable: true
title: Slide.Groups.Details
type: string
title:
nullable: true
title: Slide.Groups.Details
type: string
userCreated:
format: uuid
title: Slide.Groups.Details
type: string
userModified:
format: uuid
title: Slide.Groups.Details
type: string
type: object
Slide.ShapeTrees:
properties:
baseElementBlobUrl:
nullable: true
title: Slide.ShapeTrees
type: string
changedBaseElementBlobUrl:
nullable: true
title: Slide.ShapeTrees
type: string
groupElementId:
format: uuid
nullable: true
title: Slide.ShapeTrees
type: string
hidden:
title: Slide.ShapeTrees
type: boolean
id:
format: uuid
title: Slide.ShapeTrees
type: string
name:
nullable: true
title: Slide.ShapeTrees
type: string
ooxmlId:
format: int32
title: Slide.ShapeTrees
type: integer
packageUri:
nullable: true
title: Slide.ShapeTrees
type: string
slideId:
format: uuid
nullable: true
title: Slide.ShapeTrees
type: string
svgBlobUrl:
nullable: true
title: Slide.ShapeTrees
type: string
title:
nullable: true
title: Slide.ShapeTrees
type: string
type: object
Slide.ShapeTrees.Details:
properties:
baseElementBlobUrl:
nullable: true
title: Slide.ShapeTrees.Details
type: string
changedBaseElementBlobUrl:
nullable: true
title: Slide.ShapeTrees.Details
type: string
dateCreated:
format: date-time
title: Slide.ShapeTrees.Details
type: string
dateModified:
format: date-time
title: Slide.ShapeTrees.Details
type: string
groupElement:
$ref: "#/components/schemas/Slide.GroupElements.Details"
groupElementId:
format: uuid
nullable: true
title: Slide.ShapeTrees.Details
type: string
groupElements:
items:
$ref: "#/components/schemas/Slide.GroupElements.Details"
nullable: true
title: Slide.ShapeTrees.Details
type: array
hidden:
title: Slide.ShapeTrees.Details
type: boolean
id:
format: uuid
title: Slide.ShapeTrees.Details
type: string
name:
nullable: true
title: Slide.ShapeTrees.Details
type: string
ooxmlId:
format: int32
title: Slide.ShapeTrees.Details
type: integer
packageUri:
nullable: true
title: Slide.ShapeTrees.Details
type: string
slide:
$ref: "#/components/schemas/Slide.Slides.Details"
slideId:
format: uuid
nullable: true
title: Slide.ShapeTrees.Details
type: string
svgBlobUrl:
nullable: true
title: Slide.ShapeTrees.Details
type: string
title:
nullable: true
title: Slide.ShapeTrees.Details
type: string
userCreated:
format: uuid
title: Slide.ShapeTrees.Details
type: string
userModified:
format: uuid
title: Slide.ShapeTrees.Details
type: string
type: object
Slide.Shapes:
properties:
baseElementBlobUrl:
nullable: true
title: Slide.Shapes
type: string
changedBaseElementBlobUrl:
nullable: true
title: Slide.Shapes
type: string
flipHorizontal:
title: Slide.Shapes
type: boolean
flipVertical:
title: Slide.Shapes
type: boolean
freeFormPathXml:
nullable: true
title: Slide.Shapes
type: string
groupElementsId:
format: uuid
nullable: true
title: Slide.Shapes
type: string
height:
format: int32
title: Slide.Shapes
type: integer
hidden:
title: Slide.Shapes
type: boolean
id:
format: uuid
title: Slide.Shapes
type: string
isThemeEffect:
title: Slide.Shapes
type: boolean
isThemeFill:
title: Slide.Shapes
type: boolean
isThemeLine:
title: Slide.Shapes
type: boolean
name:
nullable: true
title: Slide.Shapes
type: string
ooxmlId:
format: int32
title: Slide.Shapes
type: integer
packageUri:
nullable: true
title: Slide.Shapes
type: string
presetTypeId:
nullable: true
title: Slide.Shapes
type: string
rotation:
format: int32
title: Slide.Shapes
type: integer
svgBlobUrl:
nullable: true
title: Slide.Shapes
type: string
width:
format: int32
title: Slide.Shapes
type: integer
xOffset:
format: int32
title: Slide.Shapes
type: integer
yOffset:
format: int32
title: Slide.Shapes
type: integer
type: object
Slide.Shapes.Details:
properties:
baseElementBlobUrl:
nullable: true
title: Slide.Shapes.Details
type: string
changedBaseElementBlobUrl:
nullable: true
title: Slide.Shapes.Details
type: string
dateCreated:
format: date-time
title: Slide.Shapes.Details
type: string
dateModified:
format: date-time
title: Slide.Shapes.Details
type: string
effect:
$ref: "#/components/schemas/Shared.Effects.Details"
fillMap:
$ref: "#/components/schemas/Shared.FillMap.Details"
flipHorizontal:
title: Slide.Shapes.Details
type: boolean
flipVertical:
title: Slide.Shapes.Details
type: boolean
freeFormPathXml:
nullable: true
title: Slide.Shapes.Details
type: string
groupElement:
$ref: "#/components/schemas/Slide.GroupElements.Details"
groupElementsId:
format: uuid
nullable: true
title: Slide.Shapes.Details
type: string
height:
format: int32
title: Slide.Shapes.Details
type: integer
hidden:
title: Slide.Shapes.Details
type: boolean
id:
format: uuid
title: Slide.Shapes.Details
type: string
isThemeEffect:
title: Slide.Shapes.Details
type: boolean
isThemeFill:
title: Slide.Shapes.Details
type: boolean
isThemeLine:
title: Slide.Shapes.Details
type: boolean
line:
$ref: "#/components/schemas/Shared.Lines.Details"
name:
nullable: true
title: Slide.Shapes.Details
type: string
ooxmlId:
format: int32
title: Slide.Shapes.Details
type: integer
packageUri:
nullable: true
title: Slide.Shapes.Details
type: string
presetTypeId:
nullable: true
title: Slide.Shapes.Details
type: string
rotation:
format: int32
title: Slide.Shapes.Details
type: integer
svgBlobUrl:
nullable: true
title: Slide.Shapes.Details
type: string
textContainer:
$ref: "#/components/schemas/Shared.TextContainer.Details"
userCreated:
format: uuid
title: Slide.Shapes.Details
type: string
userModified:
format: uuid
title: Slide.Shapes.Details
type: string
width:
format: int32
title: Slide.Shapes.Details
type: integer
xOffset:
format: int32
title: Slide.Shapes.Details
type: integer
yOffset:
format: int32
title: Slide.Shapes.Details
type: integer
type: object
Slide.SlideMasters:
properties:
id:
format: uuid
title: Slide.SlideMasters
type: string
slideId:
format: uuid
nullable: true
title: Slide.SlideMasters
type: string
type: object
Slide.SlideMasters.Details:
properties:
colorMap:
$ref: "#/components/schemas/Slide.ColorMaps.Details"
dateCreated:
format: date-time
title: Slide.SlideMasters.Details
type: string
dateModified:
format: date-time
title: Slide.SlideMasters.Details
type: string
id:
format: uuid
title: Slide.SlideMasters.Details
type: string
parentSlide:
$ref: "#/components/schemas/Slide.Slides.Details"
slideId:
format: uuid
nullable: true
title: Slide.SlideMasters.Details
type: string
userCreated:
format: uuid
title: Slide.SlideMasters.Details
type: string
userModified:
format: uuid
title: Slide.SlideMasters.Details
type: string
type: object
Slide.Slides:
properties:
baseElementBlobUrl:
nullable: true
title: Slide.Slides
type: string
changedBaseElementBlobUrl:
nullable: true
title: Slide.Slides
type: string
documentId:
format: uuid
nullable: true
title: Slide.Slides
type: string
id:
format: uuid
title: Slide.Slides
type: string
name:
nullable: true
title: Slide.Slides
type: string
number:
format: int32
title: Slide.Slides
type: integer
ooxmlId:
format: int32
title: Slide.Slides
type: integer
packageUri:
nullable: true
title: Slide.Slides
type: string
slideDocumentUrl:
nullable: true
title: Slide.Slides
type: string
svgBlobUrl:
nullable: true
title: Slide.Slides
type: string
type: object
Slide.Slides.Details:
properties:
baseElementBlobUrl:
nullable: true
title: Slide.Slides.Details
type: string
changedBaseElementBlobUrl:
nullable: true
title: Slide.Slides.Details
type: string
dateCreated:
format: date-time
title: Slide.Slides.Details
type: string
dateModified:
format: date-time
title: Slide.Slides.Details
type: string
document:
$ref: "#/components/schemas/Document.Details"
documentId:
format: uuid
nullable: true
title: Slide.Slides.Details
type: string
id:
format: uuid
title: Slide.Slides.Details
type: string
name:
nullable: true
title: Slide.Slides.Details
type: string
number:
format: int32
title: Slide.Slides.Details
type: integer
ooxmlId:
format: int32
title: Slide.Slides.Details
type: integer
packageUri:
nullable: true
title: Slide.Slides.Details
type: string
shapeTree:
$ref: "#/components/schemas/Slide.ShapeTrees.Details"
slideDocumentUrl:
nullable: true
title: Slide.Slides.Details
type: string
slideMaster:
$ref: "#/components/schemas/Slide.SlideMasters.Details"
svgBlobUrl:
nullable: true
title: Slide.Slides.Details
type: string
theme:
$ref: "#/components/schemas/Theme.Themes.Details"
userCreated:
format: uuid
title: Slide.Slides.Details
type: string
userModified:
format: uuid
title: Slide.Slides.Details
type: string
type: object
Slide.SmartArts:
properties:
baseElementBlobUrl:
nullable: true
title: Slide.SmartArts
type: string
changedBaseElementBlobUrl:
nullable: true
title: Slide.SmartArts
type: string
graphicsId:
format: uuid
nullable: true
title: Slide.SmartArts
type: string
id:
format: uuid
title: Slide.SmartArts
type: string
name:
nullable: true
title: Slide.SmartArts
type: string
packageUri:
nullable: true
title: Slide.SmartArts
type: string
svgBlobUrl:
nullable: true
title: Slide.SmartArts
type: string
type: object
Slide.SmartArts.Details:
properties:
baseElementBlobUrl:
nullable: true
title: Slide.SmartArts.Details
type: string
changedBaseElementBlobUrl:
nullable: true
title: Slide.SmartArts.Details
type: string
dateCreated:
format: date-time
title: Slide.SmartArts.Details
type: string
dateModified:
format: date-time
title: Slide.SmartArts.Details
type: string
graphicsId:
format: uuid
nullable: true
title: Slide.SmartArts.Details
type: string
id:
format: uuid
title: Slide.SmartArts.Details
type: string
name:
nullable: true
title: Slide.SmartArts.Details
type: string
packageUri:
nullable: true
title: Slide.SmartArts.Details
type: string
parentGraphic:
$ref: "#/components/schemas/Slide.Graphics.Details"
svgBlobUrl:
nullable: true
title: Slide.SmartArts.Details
type: string
userCreated:
format: uuid
title: Slide.SmartArts.Details
type: string
userModified:
format: uuid
title: Slide.SmartArts.Details
type: string
type: object
Table.Borders:
properties:
cellId:
format: uuid
nullable: true
title: Table.Borders
type: string
id:
format: uuid
title: Table.Borders
type: string
type: object
Table.Borders.Details:
properties:
bLtoTR:
$ref: "#/components/schemas/Shared.Lines.Details"
bottom:
$ref: "#/components/schemas/Shared.Lines.Details"
cell:
$ref: "#/components/schemas/Table.Cells.Details"
cellId:
format: uuid
nullable: true
title: Table.Borders.Details
type: string
dateCreated:
format: date-time
title: Table.Borders.Details
type: string
dateModified:
format: date-time
title: Table.Borders.Details
type: string
id:
format: uuid
title: Table.Borders.Details
type: string
left:
$ref: "#/components/schemas/Shared.Lines.Details"
right:
$ref: "#/components/schemas/Shared.Lines.Details"
tLtoBR:
$ref: "#/components/schemas/Shared.Lines.Details"
top:
$ref: "#/components/schemas/Shared.Lines.Details"
userCreated:
format: uuid
title: Table.Borders.Details
type: string
userModified:
format: uuid
title: Table.Borders.Details
type: string
type: object
Table.Cells:
properties:
columnId:
format: uuid
nullable: true
title: Table.Cells
type: string
columnSpan:
format: int32
title: Table.Cells
type: integer
id:
format: uuid
title: Table.Cells
type: string
isMergedHorozontal:
title: Table.Cells
type: boolean
isMergedVertical:
title: Table.Cells
type: boolean
rowId:
format: uuid
nullable: true
title: Table.Cells
type: string
rowSpan:
format: int32
title: Table.Cells
type: integer
type: object
Table.Cells.Details:
properties:
border:
$ref: "#/components/schemas/Table.Borders.Details"
column:
$ref: "#/components/schemas/Table.Columns.Details"
columnId:
format: uuid
nullable: true
title: Table.Cells.Details
type: string
columnSpan:
format: int32
title: Table.Cells.Details
type: integer
dateCreated:
format: date-time
title: Table.Cells.Details
type: string
dateModified:
format: date-time
title: Table.Cells.Details
type: string
fillMap:
$ref: "#/components/schemas/Shared.FillMap.Details"
id:
format: uuid
title: Table.Cells.Details
type: string
isMergedHorozontal:
title: Table.Cells.Details
type: boolean
isMergedVertical:
title: Table.Cells.Details
type: boolean
row:
$ref: "#/components/schemas/Table.Rows.Details"
rowId:
format: uuid
nullable: true
title: Table.Cells.Details
type: string
rowSpan:
format: int32
title: Table.Cells.Details
type: integer
textContainer:
$ref: "#/components/schemas/Shared.TextContainer.Details"
userCreated:
format: uuid
title: Table.Cells.Details
type: string
userModified:
format: uuid
title: Table.Cells.Details
type: string
type: object
Table.Columns:
properties:
id:
format: uuid
title: Table.Columns
type: string
index:
format: int32
title: Table.Columns
type: integer
tableId:
format: uuid
nullable: true
title: Table.Columns
type: string
width:
format: int64
title: Table.Columns
type: integer
type: object
Table.Columns.Details:
properties:
cells:
items:
$ref: "#/components/schemas/Table.Cells.Details"
nullable: true
title: Table.Columns.Details
type: array
dateCreated:
format: date-time
title: Table.Columns.Details
type: string
dateModified:
format: date-time
title: Table.Columns.Details
type: string
id:
format: uuid
title: Table.Columns.Details
type: string
index:
format: int32
title: Table.Columns.Details
type: integer
table:
$ref: "#/components/schemas/Table.Tables.Details"
tableId:
format: uuid
nullable: true
title: Table.Columns.Details
type: string
userCreated:
format: uuid
title: Table.Columns.Details
type: string
userModified:
format: uuid
title: Table.Columns.Details
type: string
width:
format: int64
title: Table.Columns.Details
type: integer
type: object
Table.Rows:
properties:
height:
format: int64
title: Table.Rows
type: integer
id:
format: uuid
title: Table.Rows
type: string
index:
format: int32
title: Table.Rows
type: integer
tableId:
format: uuid
nullable: true
title: Table.Rows
type: string
type: object
Table.Rows.Details:
properties:
cells:
items:
$ref: "#/components/schemas/Table.Cells.Details"
nullable: true
title: Table.Rows.Details
type: array
dateCreated:
format: date-time
title: Table.Rows.Details
type: string
dateModified:
format: date-time
title: Table.Rows.Details
type: string
height:
format: int64
title: Table.Rows.Details
type: integer
id:
format: uuid
title: Table.Rows.Details
type: string
index:
format: int32
title: Table.Rows.Details
type: integer
table:
$ref: "#/components/schemas/Table.Tables.Details"
tableId:
format: uuid
nullable: true
title: Table.Rows.Details
type: string
userCreated:
format: uuid
title: Table.Rows.Details
type: string
userModified:
format: uuid
title: Table.Rows.Details
type: string
type: object
Table.TableDataDTO:
properties:
tableData:
items:
items:
type: string
type: array
nullable: true
type: array
tableId:
format: uuid
type: string
type: object
Table.Tables:
properties:
baseElementBlobUrl:
nullable: true
title: Table.Tables
type: string
changedBaseElementBlobUrl:
nullable: true
title: Table.Tables
type: string
hasStylePart:
title: Table.Tables
type: boolean
id:
format: uuid
title: Table.Tables
type: string
name:
nullable: true
title: Table.Tables
type: string
packageUri:
nullable: true
title: Table.Tables
type: string
parentGraphicId:
format: uuid
nullable: true
title: Table.Tables
type: string
stylePartOuterXml:
nullable: true
title: Table.Tables
type: string
svgBlobUrl:
nullable: true
title: Table.Tables
type: string
type: object
Table.Tables.Details:
properties:
baseElementBlobUrl:
nullable: true
title: Table.Tables.Details
type: string
cells:
items:
$ref: "#/components/schemas/Table.Cells.Details"
nullable: true
title: Table.Tables.Details
type: array
changedBaseElementBlobUrl:
nullable: true
title: Table.Tables.Details
type: string
columns:
items:
$ref: "#/components/schemas/Table.Columns.Details"
nullable: true
title: Table.Tables.Details
type: array
dateCreated:
format: date-time
title: Table.Tables.Details
type: string
dateModified:
format: date-time
title: Table.Tables.Details
type: string
hasStylePart:
title: Table.Tables.Details
type: boolean
id:
format: uuid
title: Table.Tables.Details
type: string
name:
nullable: true
title: Table.Tables.Details
type: string
packageUri:
nullable: true
title: Table.Tables.Details
type: string
parentGraphic:
$ref: "#/components/schemas/Slide.Graphics.Details"
parentGraphicId:
format: uuid
nullable: true
title: Table.Tables.Details
type: string
rows:
items:
$ref: "#/components/schemas/Table.Rows.Details"
nullable: true
title: Table.Tables.Details
type: array
stylePartOuterXml:
nullable: true
title: Table.Tables.Details
type: string
svgBlobUrl:
nullable: true
title: Table.Tables.Details
type: string
userCreated:
format: uuid
title: Table.Tables.Details
type: string
userModified:
format: uuid
title: Table.Tables.Details
type: string
type: object
Theme.BackgroundFills:
properties:
id:
format: uuid
title: Theme.BackgroundFills
type: string
intensityId:
format: int32
title: Theme.BackgroundFills
type: integer
themeId:
format: uuid
nullable: true
title: Theme.BackgroundFills
type: string
type: object
Theme.BackgroundFills.Details:
properties:
dateCreated:
format: date-time
title: Theme.BackgroundFills.Details
type: string
dateModified:
format: date-time
title: Theme.BackgroundFills.Details
type: string
fillMap:
$ref: "#/components/schemas/Shared.FillMap.Details"
id:
format: uuid
title: Theme.BackgroundFills.Details
type: string
intensityId:
format: int32
title: Theme.BackgroundFills.Details
type: integer
theme:
$ref: "#/components/schemas/Theme.Themes.Details"
themeId:
format: uuid
nullable: true
title: Theme.BackgroundFills.Details
type: string
userCreated:
format: uuid
title: Theme.BackgroundFills.Details
type: string
userModified:
format: uuid
title: Theme.BackgroundFills.Details
type: string
type: object
Theme.Colors:
properties:
accent1:
nullable: true
title: Theme.Colors
type: string
accent2:
nullable: true
title: Theme.Colors
type: string
accent3:
nullable: true
title: Theme.Colors
type: string
accent4:
nullable: true
title: Theme.Colors
type: string
accent5:
nullable: true
title: Theme.Colors
type: string
accent6:
nullable: true
title: Theme.Colors
type: string
dark1:
nullable: true
title: Theme.Colors
type: string
dark2:
nullable: true
title: Theme.Colors
type: string
followedHyperlink:
nullable: true
title: Theme.Colors
type: string
hyperlink:
nullable: true
title: Theme.Colors
type: string
id:
format: uuid
title: Theme.Colors
type: string
light1:
nullable: true
title: Theme.Colors
type: string
light2:
nullable: true
title: Theme.Colors
type: string
name:
nullable: true
title: Theme.Colors
type: string
themeId:
format: uuid
nullable: true
title: Theme.Colors
type: string
type: object
Theme.Colors.Details:
properties:
accent1:
nullable: true
title: Theme.Colors.Details
type: string
accent2:
nullable: true
title: Theme.Colors.Details
type: string
accent3:
nullable: true
title: Theme.Colors.Details
type: string
accent4:
nullable: true
title: Theme.Colors.Details
type: string
accent5:
nullable: true
title: Theme.Colors.Details
type: string
accent6:
nullable: true
title: Theme.Colors.Details
type: string
dark1:
nullable: true
title: Theme.Colors.Details
type: string
dark2:
nullable: true
title: Theme.Colors.Details
type: string
dateCreated:
format: date-time
title: Theme.Colors.Details
type: string
dateModified:
format: date-time
title: Theme.Colors.Details
type: string
followedHyperlink:
nullable: true
title: Theme.Colors.Details
type: string
hyperlink:
nullable: true
title: Theme.Colors.Details
type: string
id:
format: uuid
title: Theme.Colors.Details
type: string
light1:
nullable: true
title: Theme.Colors.Details
type: string
light2:
nullable: true
title: Theme.Colors.Details
type: string
name:
nullable: true
title: Theme.Colors.Details
type: string
theme:
$ref: "#/components/schemas/Theme.Themes.Details"
themeId:
format: uuid
nullable: true
title: Theme.Colors.Details
type: string
userCreated:
format: uuid
title: Theme.Colors.Details
type: string
userModified:
format: uuid
title: Theme.Colors.Details
type: string
type: object
Theme.CustomColors:
properties:
hexValue:
nullable: true
title: Theme.CustomColors
type: string
id:
format: uuid
title: Theme.CustomColors
type: string
name:
nullable: true
title: Theme.CustomColors
type: string
themeId:
format: uuid
nullable: true
title: Theme.CustomColors
type: string
type: object
Theme.CustomColors.Details:
properties:
dateCreated:
format: date-time
title: Theme.CustomColors.Details
type: string
dateModified:
format: date-time
title: Theme.CustomColors.Details
type: string
hexValue:
nullable: true
title: Theme.CustomColors.Details
type: string
id:
format: uuid
title: Theme.CustomColors.Details
type: string
name:
nullable: true
title: Theme.CustomColors.Details
type: string
theme:
$ref: "#/components/schemas/Theme.Themes.Details"
themeId:
format: uuid
nullable: true
title: Theme.CustomColors.Details
type: string
userCreated:
format: uuid
title: Theme.CustomColors.Details
type: string
userModified:
format: uuid
title: Theme.CustomColors.Details
type: string
type: object
Theme.EffectMap:
properties:
id:
format: uuid
title: Theme.EffectMap
type: string
intensityId:
format: int32
title: Theme.EffectMap
type: integer
themeId:
format: uuid
nullable: true
title: Theme.EffectMap
type: string
type: object
Theme.EffectMap.Details:
properties:
dateCreated:
format: date-time
title: Theme.EffectMap.Details
type: string
dateModified:
format: date-time
title: Theme.EffectMap.Details
type: string
effect:
$ref: "#/components/schemas/Shared.Effects.Details"
id:
format: uuid
title: Theme.EffectMap.Details
type: string
intensityId:
format: int32
title: Theme.EffectMap.Details
type: integer
theme:
$ref: "#/components/schemas/Theme.Themes.Details"
themeId:
format: uuid
nullable: true
title: Theme.EffectMap.Details
type: string
userCreated:
format: uuid
title: Theme.EffectMap.Details
type: string
userModified:
format: uuid
title: Theme.EffectMap.Details
type: string
type: object
Theme.Fills:
properties:
id:
format: uuid
title: Theme.Fills
type: string
intensityId:
format: int32
title: Theme.Fills
type: integer
themeId:
format: uuid
nullable: true
title: Theme.Fills
type: string
type: object
Theme.Fills.Details:
properties:
dateCreated:
format: date-time
title: Theme.Fills.Details
type: string
dateModified:
format: date-time
title: Theme.Fills.Details
type: string
fillMap:
$ref: "#/components/schemas/Shared.FillMap.Details"
id:
format: uuid
title: Theme.Fills.Details
type: string
intensityId:
format: int32
title: Theme.Fills.Details
type: integer
theme:
$ref: "#/components/schemas/Theme.Themes.Details"
themeId:
format: uuid
nullable: true
title: Theme.Fills.Details
type: string
userCreated:
format: uuid
title: Theme.Fills.Details
type: string
userModified:
format: uuid
title: Theme.Fills.Details
type: string
type: object
Theme.Fonts:
properties:
bodyFont:
nullable: true
title: Theme.Fonts
type: string
headingFont:
nullable: true
title: Theme.Fonts
type: string
id:
format: uuid
title: Theme.Fonts
type: string
themeId:
format: uuid
nullable: true
title: Theme.Fonts
type: string
type: object
Theme.Fonts.Details:
properties:
bodyFont:
nullable: true
title: Theme.Fonts.Details
type: string
dateCreated:
format: date-time
title: Theme.Fonts.Details
type: string
dateModified:
format: date-time
title: Theme.Fonts.Details
type: string
headingFont:
nullable: true
title: Theme.Fonts.Details
type: string
id:
format: uuid
title: Theme.Fonts.Details
type: string
theme:
$ref: "#/components/schemas/Theme.Themes.Details"
themeId:
format: uuid
nullable: true
title: Theme.Fonts.Details
type: string
userCreated:
format: uuid
title: Theme.Fonts.Details
type: string
userModified:
format: uuid
title: Theme.Fonts.Details
type: string
type: object
Theme.Intensity:
properties:
description:
nullable: true
title: Theme.Intensity
type: string
id:
format: uuid
title: Theme.Intensity
type: string
name:
nullable: true
title: Theme.Intensity
type: string
typeId:
format: int32
title: Theme.Intensity
type: integer
type: object
Theme.LineMap:
properties:
id:
format: uuid
title: Theme.LineMap
type: string
intensityId:
format: int32
title: Theme.LineMap
type: integer
themeId:
format: uuid
nullable: true
title: Theme.LineMap
type: string
type: object
Theme.LineMap.Details:
properties:
dateCreated:
format: date-time
title: Theme.LineMap.Details
type: string
dateModified:
format: date-time
title: Theme.LineMap.Details
type: string
id:
format: uuid
title: Theme.LineMap.Details
type: string
intensityId:
format: int32
title: Theme.LineMap.Details
type: integer
line:
$ref: "#/components/schemas/Shared.Lines.Details"
theme:
$ref: "#/components/schemas/Theme.Themes.Details"
themeId:
format: uuid
nullable: true
title: Theme.LineMap.Details
type: string
userCreated:
format: uuid
title: Theme.LineMap.Details
type: string
userModified:
format: uuid
title: Theme.LineMap.Details
type: string
type: object
Theme.Themes:
properties:
baseElementBlobUrl:
nullable: true
title: Theme.Themes
type: string
changedBaseElementBlobUrl:
nullable: true
title: Theme.Themes
type: string
id:
format: uuid
title: Theme.Themes
type: string
name:
nullable: true
title: Theme.Themes
type: string
packageUri:
nullable: true
title: Theme.Themes
type: string
slideId:
format: uuid
nullable: true
title: Theme.Themes
type: string
type: object
Theme.Themes.Details:
properties:
backgroundFills:
items:
$ref: "#/components/schemas/Theme.BackgroundFills.Details"
nullable: true
title: Theme.Themes.Details
type: array
baseElementBlobUrl:
nullable: true
title: Theme.Themes.Details
type: string
changedBaseElementBlobUrl:
nullable: true
title: Theme.Themes.Details
type: string
colors:
$ref: "#/components/schemas/Theme.Colors.Details"
customColors:
items:
$ref: "#/components/schemas/Theme.CustomColors.Details"
nullable: true
title: Theme.Themes.Details
type: array
dateCreated:
format: date-time
title: Theme.Themes.Details
type: string
dateModified:
format: date-time
title: Theme.Themes.Details
type: string
effectMaps:
items:
$ref: "#/components/schemas/Theme.EffectMap.Details"
nullable: true
title: Theme.Themes.Details
type: array
fills:
items:
$ref: "#/components/schemas/Theme.Fills.Details"
nullable: true
title: Theme.Themes.Details
type: array
fonts:
$ref: "#/components/schemas/Theme.Fonts.Details"
id:
format: uuid
title: Theme.Themes.Details
type: string
lineMaps:
items:
$ref: "#/components/schemas/Theme.LineMap.Details"
nullable: true
title: Theme.Themes.Details
type: array
name:
nullable: true
title: Theme.Themes.Details
type: string
packageUri:
nullable: true
title: Theme.Themes.Details
type: string
slide:
$ref: "#/components/schemas/Slide.Slides.Details"
slideId:
format: uuid
nullable: true
title: Theme.Themes.Details
type: string
userCreated:
format: uuid
title: Theme.Themes.Details
type: string
userModified:
format: uuid
title: Theme.Themes.Details
type: string
type: object kin-openapi-0.124.0/openapi3/testdata/issue831/ 0000775 0000000 0000000 00000000000 14604223742 0021007 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/issue831/path.yml 0000664 0000000 0000000 00000000062 14604223742 0022464 0 ustar 00root root 0000000 0000000 get:
responses:
"200":
description: OK kin-openapi-0.124.0/openapi3/testdata/issue831/testref.internalizepath.openapi.yml 0000664 0000000 0000000 00000000777 14604223742 0030053 0 ustar 00root root 0000000 0000000 openapi: "3.0.3"
info:
title: Recursive refs example
version: "1.0"
paths:
/bar:
$ref: ./path.yml
/foo:
post:
requestBody:
content:
application/json:
schema:
$ref: "#/components/requestBodies/test/content/application~1json/schema"
responses:
'200':
description: Expected response to a valid request
components:
requestBodies:
test:
content:
application/json:
schema:
type: string kin-openapi-0.124.0/openapi3/testdata/issue831/testref.internalizepath.openapi.yml.internalized.yml 0000664 0000000 0000000 00000002355 14604223742 0033334 0 ustar 00root root 0000000 0000000 {
"openapi": "3.0.3",
"info": {
"title": "Recursive refs example",
"version": "1.0"
},
"paths": {
"/bar": {
"get": {
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/foo": {
"post": {
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/requestBodies/test/content/application~1json/schema"
}
}
}
},
"responses": {
"200": {
"description": "Expected response to a valid request"
}
}
}
}
},
"components": {
"requestBodies": {
"test": {
"content": {
"application/json": {
"schema": {
"type": "string"
}
}
}
}
}
}
} kin-openapi-0.124.0/openapi3/testdata/link-example.yaml 0000664 0000000 0000000 00000012160 14604223742 0022675 0 ustar 00root root 0000000 0000000 openapi: 3.0.0
info:
title: Link Example
version: 1.0.0
paths:
/2.0/users/{username}:
get:
operationId: getUserByName
parameters:
- name: username
in: path
required: true
schema:
type: string
responses:
'200':
description: The User
content:
application/json:
schema:
$ref: '#/components/schemas/user'
links:
userRepositories:
$ref: '#/components/links/UserRepositories'
/2.0/repositories/{username}:
get:
operationId: getRepositoriesByOwner
parameters:
- name: username
in: path
required: true
schema:
type: string
responses:
'200':
description: repositories owned by the supplied user
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/repository'
links:
userRepository:
$ref: '#/components/links/UserRepository'
/2.0/repositories/{username}/{slug}:
get:
operationId: getRepository
parameters:
- name: username
in: path
required: true
schema:
type: string
- name: slug
in: path
required: true
schema:
type: string
responses:
'200':
description: The repository
content:
application/json:
schema:
$ref: '#/components/schemas/repository'
links:
repositoryPullRequests:
$ref: '#/components/links/RepositoryPullRequests'
/2.0/repositories/{username}/{slug}/pullrequests:
get:
operationId: getPullRequestsByRepository
parameters:
- name: username
in: path
required: true
schema:
type: string
- name: slug
in: path
required: true
schema:
type: string
- name: state
in: query
schema:
type: string
enum:
- open
- merged
- declined
responses:
'200':
description: an array of pull request objects
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/pullrequest'
/2.0/repositories/{username}/{slug}/pullrequests/{pid}:
get:
operationId: getPullRequestsById
parameters:
- name: username
in: path
required: true
schema:
type: string
- name: slug
in: path
required: true
schema:
type: string
- name: pid
in: path
required: true
schema:
type: string
responses:
'200':
description: a pull request object
content:
application/json:
schema:
$ref: '#/components/schemas/pullrequest'
links:
pullRequestMerge:
$ref: '#/components/links/PullRequestMerge'
/2.0/repositories/{username}/{slug}/pullrequests/{pid}/merge:
post:
operationId: mergePullRequest
parameters:
- name: username
in: path
required: true
schema:
type: string
- name: slug
in: path
required: true
schema:
type: string
- name: pid
in: path
required: true
schema:
type: string
responses:
'204':
description: the PR was successfully merged
components:
links:
UserRepositories:
# returns array of '#/components/schemas/repository'
operationId: getRepositoriesByOwner
parameters:
username: $response.body#/username
UserRepository:
# returns '#/components/schemas/repository'
operationId: getRepository
parameters:
username: $response.body#/owner/username
slug: $response.body#/slug
RepositoryPullRequests:
# returns '#/components/schemas/pullrequest'
operationId: getPullRequestsByRepository
parameters:
username: $response.body#/owner/username
slug: $response.body#/slug
PullRequestMerge:
# executes /2.0/repositories/{username}/{slug}/pullrequests/{pid}/merge
operationId: mergePullRequest
parameters:
username: $response.body#/author/username
slug: $response.body#/repository/slug
pid: $response.body#/id
schemas:
user:
type: object
properties:
username:
type: string
uuid:
type: string
repository:
type: object
properties:
slug:
type: string
owner:
$ref: '#/components/schemas/user'
pullrequest:
type: object
properties:
id:
type: integer
title:
type: string
repository:
$ref: '#/components/schemas/repository'
author:
$ref: '#/components/schemas/user'
kin-openapi-0.124.0/openapi3/testdata/lxkns.yaml 0000664 0000000 0000000 00000123151 14604223742 0021451 0 ustar 00root root 0000000 0000000 # https://raw.githubusercontent.com/thediveo/lxkns/71e8fb5e40c612ecc89d972d211221137e92d5f0/api/openapi-spec/lxkns.yaml
openapi: 3.0.2
security:
- {}
info:
title: lxkns
version: 0.22.0
description: |-
Discover Linux-kernel namespaces, almost everywhere in a Linux host. Also look
for mount points and their hierarchy, as well as for containers.
contact:
url: 'https://github.com/thediveo/lxkns'
license:
name: Apache 2.0
url: 'https://www.apache.org/licenses/LICENSE-2.0'
servers:
-
url: /api
description: lxkns as-a-service
paths:
/processes:
summary: Process discovery
get:
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/ProcessTable'
description: |-
Returns information about all processes and their position within the process
tree.
summary: Linux processes
description: |-
Map of all processes in the process tree, with the keys being the PIDs in
decimal string format.
/pidmap:
summary: Discover the translation of PIDs between PID namespaces
get:
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/PIDMap'
description: |-
The namespaced PIDs of processes. For each process, the PIDs in their PID
namespaces along the PID namespace hierarchy are returned.
summary: PID translation data
description: |
Discovers the PIDs that processes have in different PID namespaces,
according to the hierarchy of PID namespaces.
> **IMPORTANT:** The order of processes is undefined. However, the order of
> the namespaced PIDs of a particular process is well-defined.
/namespaces:
summary: Namespace discovery (includes process discovery for technical reasons)
get:
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/DiscoveryResult'
description: The discovered namespaces and processes.
summary: Linux kernel namespaces
description: |-
Information about the Linux-kernel namespaces and how they relate to processes
and vice versa.
components:
schemas:
PIDMap:
title: Root Type for PIDMap
description: |-
A "map" of the PIDs of processes in PID namespaces for translating a specific
PID from one PID namespace into another PID namespace.
> **IMPORTANT:** The order of *processes* is undefined. However, the order of
> the namespaced PIDs of a particular process is well-defined: from the PID in
> the process' own PID namespace up the hierarchy to the PID in the initial
> PID namespace.
The PID map is represented in a "condensed" format, which is designed to
minimize transfer volume. Consuming applications thus might want to transfer
this external representation into a performance-optimized internal
representation, optimized for translating PIDs.
type: array
items:
$ref: '#/components/schemas/NamespacedPIDs'
example:
-
-
pid: 12345
nsid: 4026531905
-
pid: 1
nsid: 4026538371
-
-
pid: 666
nsid: 4026538371
NamespacedPID:
title: Root Type for NamespacedPID
description: |-
A process identifier (PID) valid only in the accompanying PID namespace,
referenced by the ID (inode number) of the PID namespace. Outside that PID
namespace the PID is invalid and might be confused with some other process that
happens to have the same PID in the other PID namespace. For instance, PID 1
can be found not only in the initial PID namespace, but usually also in all
other PID namespaces, but referencing completely different processes each time.
required:
- pid
- nsid
type: object
properties:
pid:
description: a process identifier
type: integer
nsid:
format: int64
description: |-
a PID namespace identified and referenced by its inode number (without any
device number).
type: integer
example:
pid: 1
nsid: 4026531905
NamespacedPIDs:
description: |-
The list of namespaced PIDs of a process, ordered according to the PID
namespace hierarchy the process is in. The order is from the "bottom-most" PID
namespace a particular process is joined to up to the initial PID namespace.
Thus, the PID in the initial PID namespace always comes last.
type: array
items:
$ref: '#/components/schemas/NamespacedPID'
example:
-
pid: 12345
nsid: 4026531905
-
pid: 1
nsid: 4026532382
Process:
description: |-
Information about a specific process, such as its PID, name, and command line
arguments, the references (IDs) of the namespaces the process is joined to.
required:
- pid
- ppid
- name
- cmdline
- starttime
- namespaces
- cpucgroup
# - fridgecgroup
# - fridgefrozen
type: object
properties:
pid:
format: int32
description: The process identifier (PID) of this process.
type: integer
ppid:
format: int32
description: |-
The PID of the parent process, or 0 if there is no parent process. On Linux, the
only processes without a parent are the initial process PID 1 and the PID 2
kthreadd kernel threads "process".
type: integer
name:
description: |-
A synthesized name of the process:
- a name set by the process itself,
- a name derived from the command line of the process.
type: string
cmdline:
description: |-
The command line arguments of the process, including the process binary file
name. Taken from /proc/$PID/cmdline, see also
[https://man7.org/linux/man-pages/man5/proc.5.html](proc(5)).
type: array
items:
type: string
starttime:
format: int64
description: |-
The time this process started after system boot and expressed in clock ticks.
It is taken from /proc/$PID/stat, see also
[https://man7.org/linux/man-pages/man5/proc.5.html](proc(5)).
type: integer
cpucgroup:
description: |-
The (CPU) cgroup (control group) path name in the hierarchy this process is in. The
path name does not specify the root mount path of the complete hierarchy, but
only the (pseudo) absolute path starting from the root of the particular (v1) or
unified (v2) cgroup hierarchy.
type: string
namespaces:
$ref: '#/components/schemas/NamespacesSet'
description: |-
References the namespaces this process is joined to, in form of the namespace
IDs (inode numbers).
fridgecgroup:
description: The freezer cgroup path name in the hierarchy this process is in.
type: string
fridgefrozen:
description: The effective freezer state of this process.
type: boolean
example:
namespaces:
mnt: 4026531840
cgroup: 4026531835
uts: 4026531838
ipc: 4026531839
user: 4026531837
pid: 4026531836
net: 4026531905
pid: 1
ppid: 0
name: systemd
cmdline:
- /sbin/init
- fixrtc
- splash
starttime: 0
cpucgroup: /init.scope
ProcessTable:
description: |-
Information about all processes in the process tree, with each process item
being keyed by its PID in string form. Besides information about the process
itself and its position in the process tree, the processes also reference the
namespaces they are currently joined to.
type: object
additionalProperties:
$ref: '#/components/schemas/Process'
example:
'1':
namespaces:
mnt: 4026531840
cgroup: 4026531835
uts: 4026531838
ipc: 4026531839
user: 4026531837
pid: 4026531836
net: 4026531905
pid: 1
ppid: 0
name: systemd
cmdline:
- /sbin/init
- fixrtc
- splash
starttime: 0
cpucgroup: /init.scope
'137024':
namespaces:
mnt: 4026532517
cgroup: 4026531835
uts: 4026531838
ipc: 4026531839
user: 4026532518
pid: 4026531836
net: 4026531905
pid: 137024
ppid: 1
name: upowerd
cmdline:
- /usr/lib/upower/upowerd
starttime: 3132568
cpucgroup: /system.slice/upower.service
DiscoveryResult:
description: |-
The discovered namespaces and processes with their mutual relationships, and
optionally PID translation data.
required:
- namespaces
- processes
- containers
- container-engines
- container-groups
type: object
properties:
processes:
$ref: '#/components/schemas/ProcessTable'
description: 'Information about all processes, including the process hierarchy.'
namespaces:
$ref: '#/components/schemas/NamespacesDict'
description: Map of namespaces.
pidmap:
$ref: '#/components/schemas/PIDMap'
description: Data for translating PIDs between different PID namespaces.
options:
$ref: '#/components/schemas/DiscoveryOptions'
description: The options specified for discovery.
mounts:
$ref: '#/components/schemas/NamespacedMountPaths'
description: Map of mount namespace'd mount paths with mount points.
containers:
$ref: '#/components/schemas/ContainerMap'
description: Discovered containers.
container-engines:
$ref: '#/components/schemas/ContainerEngineMap'
description: Container engines managing the discovered containers.
container-groups:
$ref: '#/components/schemas/ContainerGroupMap'
description: Groups of containers.
example:
discovery-options:
skipped-procs: false
skipped-tasks: false
skipped-fds: false
skipped-bindmounts: false
skipped-hierarchy: false
skipped-ownership: false
skipped-freezer: false
scanned-namespace-types:
- time
- mnt
- cgroup
- uts
- ipc
- user
- pid
- net
namespaces:
'4026531835':
nsid: 4026531835
type: cgroup
owner: 4026531837
reference: /proc/2/ns/cgroup
leaders:
- 2
- 1
'4026531836':
nsid: 4026531836
type: pid
owner: 4026531837
reference: /proc/2/ns/pid
leaders:
- 2
- 1
children:
- 4026532338
'4026531837':
nsid: 4026531837
type: user
reference: /proc/1/ns/user
leaders:
- 1
- 2
children:
- 4026532518
user-id: 0
'4026531838':
nsid: 4026531838
type: uts
owner: 4026531837
reference: /proc/2/ns/uts
leaders:
- 2
- 1
'4026531839':
nsid: 4026531839
type: ipc
owner: 4026531837
reference: /proc/2/ns/ipc
leaders:
- 2
- 1
'4026532268':
nsid: 4026532268
type: mnt
owner: 4026531837
reference: /proc/1761/ns/mnt
leaders:
- 1761
'4026532324':
nsid: 4026532324
type: uts
owner: 4026531837
reference: /proc/1781/ns/uts
leaders:
- 1781
'4026532337':
nsid: 4026532337
type: ipc
owner: 4026531837
reference: /proc/33536/ns/ipc
leaders:
- 33536
'4026532340':
nsid: 4026532340
type: net
owner: 4026531837
reference: /proc/33536/ns/net
leaders:
- 33536
'4026532398':
nsid: 4026532398
type: pid
owner: 4026531837
reference: /proc/34110/ns/pid
leaders:
- 34110
parent: 4026532338
'4026532400':
nsid: 4026532400
type: net
owner: 4026531837
reference: /proc/34110/ns/net
leaders:
- 34110
'4026532517':
nsid: 4026532517
type: mnt
owner: 4026531837
reference: /proc/137024/ns/mnt
leaders:
- 137024
'4026532518':
nsid: 4026532518
type: user
reference: /proc/137024/ns/user
leaders:
- 137024
parent: 4026531837
user-id: 0
processes:
'1':
namespaces:
mnt: 4026531840
cgroup: 4026531835
uts: 4026531838
ipc: 4026531839
user: 4026531837
pid: 4026531836
net: 4026531905
pid: 1
ppid: 0
name: systemd
cmdline:
- /sbin/init
- fixrtc
- splash
starttime: 0
cpucgroup: /init.scope
'17':
namespaces:
mnt: 4026531840
cgroup: 4026531835
uts: 4026531838
ipc: 4026531839
user: 4026531837
pid: 4026531836
net: 4026531905
pid: 17
ppid: 2
name: migration/1
cmdline:
- ''
starttime: 0
cpucgroup: ''
'1692':
namespaces:
mnt: 4026532246
cgroup: 4026531835
uts: 4026532247
ipc: 4026531839
user: 4026531837
pid: 4026531836
net: 4026531905
pid: 1692
ppid: 1
name: systemd-timesyn
cmdline:
- /lib/systemd/systemd-timesyncd
starttime: 2032
cpucgroup: /system.slice/systemd-timesyncd.service
Namespace:
description: |-
Information about a single Linux-kernel namespace. Depending on the extent of
the discovery, not all namespace types might have been discovered, or data might
be missing about the PID and user namespace hierarchies as well as which user
namespace owns other namespaces.
For more details, please see also:
https://man7.org/linux/man-pages/man7/namespaces.7.html.
required:
- type
- nsid
type: object
properties:
nsid:
format: int64
description: |-
Identifier of this namespace: an inode number.
- lxkns only uses the inode number in the API, following current Linux kernel
and CLI tool practise, which generally identify individual namespaces only by
inode numbers (and leaving out the device number).
- Namespace identifiers are not UUIDs, but instead reused by the kernel after a
namespace has been destroyed.
type: integer
type:
$ref: '#/components/schemas/NamespaceType'
description: Type of this namespace.
owner:
format: int64
description: The ID of the owning user namespace.
type: integer
reference:
description: |-
File system reference to the namespace, if available. The hierarchical PID and
user namespaces can also exist without any file system references, as long as
there are still child namespaces present for such a PID or user namespace.
type: array
items:
type: string
leaders:
description: |-
List of PIDs of "leader" processes joined to this namespace.
Instead of listing all processes joined to this namespace, lxkns only lists the
"most senior" processes: these processes are the highest processes in the
process tree still joined to a namespace. Child processes also joined to this
namespace can then be found using the child process relations from the process
table information.
type: array
items:
format: int32
type: integer
ealdorman:
format: int32
description: PID of the most senior leader process joined to this namespace.
type: integer
parent:
format: int64
description: 'Only for PID and user namespaces: the ID of the parent namespace.'
type: integer
user-id:
description: |-
Only for user namespaces: the UID of the Linux user who created this user
namespace.
type: integer
user-name:
description: |-
Only for user namespaces: the name of the Linux user who created this user
namespace.
type: string
children:
description: 'For user and PID namespaces: the list of child namespace IDs.'
type: array
items:
format: int64
type: integer
possessions:
description: 'Only user namespaces: list of namespace IDs of owned (non-user) namespaces.'
type: array
items:
format: int64
type: integer
example:
'4026532338':
nsid: 4026532338
type: pid
owner: 4026531837
reference: /proc/33536/ns/pid
leaders:
- 33536
parent: 4026531836
children:
- 4026532398
NamespaceType:
description: |-
Type of Linux-kernel namespace. For more information about namespaces, please
see also: https://man7.org/linux/man-pages/man7/namespaces.7.html.
enum:
- cgroup
- ipc
- net
- mnt
- pid
- user
- uts
- time
type: string
example: 'net'
NamespacesDict:
description: |
"Dictionary" or "map" of Linux-kernel namespaces, keyed by their namespace IDs in stringified
form. Contrary to what the term "namespace" might suggest, namespaces do not
have names but are identified by their (transient) inode numbers.
> **Note:** following current best practice of the Linux kernel and CLI tools,
> namespace references are only in the form of the inode number, without the
> device number.
For further details, please see also:
https://man7.org/linux/man-pages/man7/namespaces.7.html.
type: object
additionalProperties:
$ref: '#/components/schemas/Namespace'
example:
'4026532267':
nsid: 4026532267
type: mnt
owner: 4026531837
reference: /proc/1714/ns/mnt
leaders:
- 1714
'4026532268':
nsid: 4026532268
type: mnt
owner: 4026531837
reference: /proc/1761/ns/mnt
leaders:
- 1761
DiscoveryOptions:
title: Root Type for DiscoveryOptions
description: ''
required:
- scanned-namespace-types
type: object
properties:
from-procs:
type: boolean
from-tasks:
type: boolean
from-fds:
type: boolean
from-bindmounts:
type: boolean
with-hierarchy:
type: boolean
with-ownership:
type: boolean
with-freezer:
description: |-
true if the discovery of the (effective) freezer states of processes has been
skipped, so that all processes always appear to be "thawed" (running).
type: boolean
scanned-namespace-types:
description: |-
List of namespace types included in the discovery. This information might help
consuming tools to understand which types of namespaces were scanned and which
were not scanned for at all.
type: array
items:
$ref: '#/components/schemas/NamespaceType'
with-mounts:
description: true if mount namespace'd mount paths with mount points were discovered.
type: boolean
labels:
description: |-
Dictionary of key=value pairs passed to decorators to optionally control the
decoration of discovered containers.
example:
skipped-procs: false
skipped-tasks: false
skipped-fds: false
skipped-bindmounts: false
skipped-hierarchy: false
skipped-ownership: false
skipped-freezer: false
scanned-namespace-types:
- time
- mnt
- cgroup
- uts
- ipc
- user
- pid
- net
NamespacesSet:
description: |-
The set of 7 namespaces (8 namespaces since Linux 5.6+) every process is always
joined to. The namespaces are referenced by their IDs (inode numbers):
- cgroup namespace
- IPC namespace
- network namespace
- mount namespace
- PID namespace
- user namespace
- UTS namespace
- time namespace (Linux kernel 5.6+)
> **Note:** Since lxkns doesn't officially support Linux kernels before 4.9
> all namespaces except the "time" namespace can safely be assumed to be
> always present.
For more details about namespaces, please see also:
https://man7.org/linux/man-pages/man7/namespaces.7.html.
type: object
properties:
cgroup:
format: int64
description: |-
References a cgroup namespace by ID (inode number). Please see also:
https://www.man7.org/linux/man-pages/man7/cgroup_namespaces.7.html.
type: integer
ipc:
format: int64
description: |-
References an IPC namespace by ID (inode number). Please see also:
https://www.man7.org/linux/man-pages/man7/ipc_namespaces.7.html.
type: integer
net:
format: int64
description: |-
References a network namespace by ID (inode number). Please see also:
https://www.man7.org/linux/man-pages/man7/network_namespaces.7.html.
type: integer
mnt:
format: int64
description: |-
References a mount namespace by ID (inode number). Please see also:
https://www.man7.org/linux/man-pages/man7/mount_namespaces.7.html.
type: integer
pid:
format: int64
description: |-
References a PID namespace by ID (inode number). Please see also:
https://www.man7.org/linux/man-pages/man7/pid_namespaces.7.html.
type: integer
user:
format: int64
description: |-
References a user namespace by ID (inode number). Please see also:
https://www.man7.org/linux/man-pages/man7/user_namespaces.7.html.
type: integer
uts:
format: int64
description: |-
References a UTS (*nix timesharing system) namespace by ID (inode number).
Please see also: https://www.man7.org/linux/man-pages/man7/uts_namespaces.7.html.
type: integer
time:
format: int64
description: |-
References a (monotonous) time namespace by ID (inode number). Time namespaces
are only supported on Linux kernels 5.6 or later. Please see also:
https://www.man7.org/linux/man-pages/man7/time_namespaces.7.html.
type: integer
example:
mnt: 4026531840
cgroup: 4026531835
uts: 4026531838
ipc: 4026531839
user: 4026531837
pid: 4026531836
net: 4026531905
MountPoint:
description: |-
Information about a mount point as discovered from the proc filesystem. See also
[proc(5)](https://man7.org/linux/man-pages/man5/procfs.5.html), and details about
`/proc/[PID]/mountinfo` in particular.
required:
- mountid
- parentid
- major
- minor
- root
- mountpoint
- mountoptions
- tags
- source
- fstype
- superoptions
- hidden
type: object
properties:
parentid:
description: |-
ID of the parent mount. Please note that the parent mount might be outside a
mount namespace.
type: integer
mountid:
description: 'unique ID for the mount, might be reused after umount(2).'
type: integer
major:
description: major ID for the st_dev for files on this filesystem.
type: integer
minor:
description: minor ID for the st_dev for filed on this filesystem.
type: integer
root:
description: pathname of the directory in the filesystem which forms the root of this mount.
type: string
mountpoint:
description: pathname of the mount point relative to root directory of the process.
type: string
mountoptions:
description: mount options specific to this mount.
type: array
items:
type: string
tags:
$ref: '#/components/schemas/MountTags'
description: |-
optional tags with even more optional values. Tags cannot be a single hyphen
"-".
fstype:
description: 'filesystem type in the form "type[.subtype]".'
type: string
source:
description: filesystem-specific information or "none".
type: string
superoptions:
description: per-superblock options.
type: string
hidden:
description: |-
true if this mount point is hidden by an "overmount" either at the same mount
path or higher up the path hierarchy.
type: boolean
MountTags:
description: |-
dictionary of mount point tags with optional values. Tag names cannot be a single
hyphen "-".
type: object
additionalProperties:
type: string
MountPath:
description: |-
path of one or more mount points in the Virtual File System (VFS). In case of
multiple mount points at the same path, only at most one of them can be visible
and all others (or all in case of an overmount higher up the path) will be hidden.
required:
- mounts
- pathid
- parentid
type: object
properties:
mounts:
description: one or more mount points at this path in the Virtual File System (VFS).
type: array
items:
$ref: '#/components/schemas/MountPoint'
pathid:
description: 'unique mount path identifier, per mount namespace.'
type: integer
parentid:
description: 'identifier of parent mount path, if any, otherwise 0.'
type: integer
MountPathsDict:
description: |-
"Dictionary" or "map" of mount paths with their corresponding mount points, keyed
by the mount paths.
Please note that additionally the mount path entries are organized in a "sparse"
hierarchy with the help of mount path identifiers (these are user-space generated
by lxkns).
type: object
additionalProperties:
$ref: '#/components/schemas/MountPath'
NamespacedMountPaths:
description: 'the mount paths of each discovered mount namespace, separated by mount namespace.'
type: object
additionalProperties:
$ref: '#/components/schemas/MountPathsDict'
Container:
description: 'Alive container with process(es), either running or paused.'
required:
- id
- name
- type
- flavor
- pid
- paused
- labels
- groups
- engine
type: object
properties:
id:
description: Container identifier
type: string
name:
description: 'Container name as opposed to its id, might be the same for some container engines.'
type: string
type:
description: 'Type of container identifier, such as "docker.com", et cetera.'
type: string
flavor:
description: 'Flavor of container, might be the same as the type or different.'
type: string
pid:
description: Process ID of initial container process.
type: integer
paused:
description: Indicates whether the container is running or paused.
type: boolean
labels:
$ref: '#/components/schemas/Labels'
description: Label name=value pairs attached to this container.
groups:
description: |-
List of group reference identifiers this container is a member of. For instance,
(Docker) composer projects, Kubernetes pods, ...
type: array
items:
type: integer
engine:
description: Reference identifier of the container engine managing this container.
type: integer
Labels:
description: 'Dictionary (map) of KEY=VALUE pairs, with KEY and VALUE both strings.'
type: object
additionalProperties:
type: string
ContainerEngine:
description: Information about a container engine managing a set of discovered containers.
required:
- id
- type
- version
- api
- pid
- containers
type: object
properties:
id:
description: 'Container engine instance identifier, such as UUID, unique string, et cetera.'
type: string
type:
description: 'Engine type identifier, such as "containerd.io", et cetera.'
type: string
version:
description: 'Engine version information.'
type: string
api:
description: Engine API path.
type: string
pid:
description: 'Engine''s PID (in initial PID namespace) when known, otherwise zero.'
type: integer
containers:
description: List of reference IDs (=PIDs) of containers managed by this engine.
type: array
items:
type: integer
ContainerGroup:
description: A group of containers somehow related.
required:
- name
- type
- flavor
- containers
- labels
type: object
properties:
name:
description: |-
Name of group, such as a (Docker) composer project name, Kubernetes pod
namespace/name, et cetera.
type: string
type:
description: Group type identifier.
type: string
flavor:
description: 'Group flavor identifier, might be identical with group type identifier.'
type: string
containers:
description: List of reference IDs (=PIDs) of containers belonging to this group.
type: array
items:
type: integer
labels:
$ref: '#/components/schemas/Labels'
description: Additional KEY=VALUE information.
ContainerMap:
description: |-
Maps container PIDs to containers. Container PIDs are the PIDs of initial
container processes only, but not any child processes.
type: object
additionalProperties:
$ref: '#/components/schemas/Container'
ContainerEngineMap:
description: Maps reference IDs to container engines.
type: object
additionalProperties:
$ref: '#/components/schemas/ContainerEngine'
ContainerGroupMap:
description: Maps reference IDs to container groups.
type: object
additionalProperties:
$ref: '#/components/schemas/ContainerGroup'
kin-openapi-0.124.0/openapi3/testdata/main.yaml 0000664 0000000 0000000 00000000175 14604223742 0021236 0 ustar 00root root 0000000 0000000 openapi: "3.0.0"
info:
title: "test file"
version: "n/a"
paths:
/testpath:
$ref: "testpath.yaml#/paths/~1testpath"
kin-openapi-0.124.0/openapi3/testdata/my-openapi.json 0000664 0000000 0000000 00000000447 14604223742 0022401 0 ustar 00root root 0000000 0000000 {
"openapi": "3.0.2",
"info": {
"title": "My API",
"version": "0.1.0"
},
"paths": {
"/foo": {
"get": {
"responses": {
"200": {
"$ref": "my-other-openapi.json#/components/responses/DefaultResponse"
}
}
}
}
}
}
kin-openapi-0.124.0/openapi3/testdata/my-other-openapi.json 0000664 0000000 0000000 00000001147 14604223742 0023516 0 ustar 00root root 0000000 0000000 {
"openapi": "3.0.2",
"info": {
"title": "My other API",
"version": "0.1.0"
},
"components": {
"schemas": {
"DefaultObject": {
"type": "object",
"properties": {
"foo": {
"type": "string"
},
"bar": {
"type": "integer"
}
}
}
},
"responses": {
"DefaultResponse": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DefaultObject"
}
}
}
}
}
}
}
kin-openapi-0.124.0/openapi3/testdata/nesteddir/ 0000775 0000000 0000000 00000000000 14604223742 0021404 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/nesteddir/nestedcomponents.openapi.json 0000664 0000000 0000000 00000002475 14604223742 0027331 0 ustar 00root root 0000000 0000000 {
"openapi": "3.0.0",
"info": {
"title": "",
"version": "1"
},
"paths": {},
"components": {
"schemas": {
"CustomTestSchema": {
"$ref": "nestedcomponentsref.openapi.json#/components/schemas/Name"
}
},
"responses": {
"CustomTestResponse": {
"$ref": "nestedcomponentsref.openapi.json#/components/responses/Name"
}
},
"parameters": {
"CustomTestParameter": {
"$ref": "nestedcomponentsref.openapi.json#/components/parameters/Name"
}
},
"examples": {
"CustomTestExample": {
"$ref": "nestedcomponentsref.openapi.json#/components/examples/Name"
}
},
"requestBodies": {
"CustomTestRequestBody": {
"$ref": "nestedcomponentsref.openapi.json#/components/requestBodies/Name"
}
},
"headers": {
"CustomTestHeader": {
"$ref": "nestedcomponentsref.openapi.json#/components/headers/Name"
}
},
"securitySchemes": {
"CustomTestSecurityScheme": {
"$ref": "nestedcomponentsref.openapi.json#/components/securitySchemes/Name"
}
}
}
} kin-openapi-0.124.0/openapi3/testdata/nesteddir/nestedcomponentsref.openapi.json 0000664 0000000 0000000 00000001710 14604223742 0030015 0 ustar 00root root 0000000 0000000 {
"openapi": "3.0.0",
"info": {
"title": "",
"version": "1"
},
"paths": {},
"components": {
"schemas": {
"Name": {
"type": "string"
}
},
"responses": {
"Name": {
"description": "description"
}
},
"parameters": {
"Name": {
"name": "id",
"in": "header"
}
},
"examples": {
"Name": {
"description": "description"
}
},
"requestBodies": {
"Name": {
"content": {}
}
},
"headers": {
"Name": {
"description": "description"
}
},
"securitySchemes": {
"Name": {
"type": "cookie",
"description": "description"
}
}
}
} kin-openapi-0.124.0/openapi3/testdata/oai_v3_stoplight.json 0000664 0000000 0000000 00005712071 14604223742 0023610 0 ustar 00root root 0000000 0000000 {
"components": {
"parameters": {
"trait_authorizationHeader_Authorization": {
"in": "header",
"name": "Authorization",
"required": true,
"schema": {
"default": "Bearer <>",
"type": "string"
}
},
"trait_automationQueryParams_group_by": {
"description": "Automations can have multiple steps. Including `step_id` as a `group_by` metric allows further granularity of stats.",
"explode": false,
"in": "query",
"name": "group_by",
"schema": {
"items": {
"enum": [
"step_id"
],
"type": "string"
},
"type": "array"
},
"style": "form"
},
"trait_automationQueryParams_step_ids": {
"description": "Comma-separated list of `step_ids` that you want the link stats for.",
"explode": false,
"in": "query",
"name": "step_ids",
"schema": {
"items": {
"format": "uuid",
"type": "string"
},
"type": "array",
"uniqueItems": true
},
"style": "form"
},
"trait_baseParams_aggregated_by": {
"description": "Dictates how the stats are time-sliced. Currently, `\"total\"` and `\"day\"` are supported.",
"in": "query",
"name": "aggregated_by",
"schema": {
"default": "total",
"enum": [
"day",
"total"
],
"type": "string"
}
},
"trait_baseParams_end_date": {
"description": "Format: `YYYY-MM-DD`.If this parameter is included, the stats' end date is included in the search.",
"in": "query",
"name": "end_date",
"schema": {
"default": "2021-12-31",
"format": "date",
"type": "string"
}
},
"trait_baseParams_start_date": {
"description": "Format: `YYYY-MM-DD`. If this parameter is included, the stats' start date is included in the search.",
"in": "query",
"name": "start_date",
"schema": {
"default": "2021-01-01",
"format": "date",
"type": "string"
}
},
"trait_baseParams_timezone": {
"description": "[IANA Area/Region](https://en.wikipedia.org/wiki/Tz_database#Names_of_time_zones) string representing the timezone in which the stats are to be presented, e.g., \"America/Chicago\".",
"in": "query",
"name": "timezone",
"schema": {
"default": "UTC",
"type": "string"
}
},
"trait_designsQueryStrings_page_size": {
"description": "number of results to return",
"in": "query",
"name": "page_size",
"schema": {
"default": 100,
"minimum": 0,
"type": "integer"
}
},
"trait_designsQueryStrings_page_token": {
"description": "token corresponding to a specific page of results, as provided by metadata",
"in": "query",
"name": "page_token",
"schema": {
"type": "string"
}
},
"trait_designsQueryStrings_summary": {
"description": "set to false to return all fields",
"in": "query",
"name": "summary",
"schema": {
"default": true,
"type": "boolean"
}
},
"trait_onBehalfOfSubuser_on-behalf-of": {
"in": "header",
"name": "on-behalf-of",
"schema": {
"default": "The subuser's username. This header generates the API call as if the subuser account was making the call.",
"type": "string"
}
},
"trait_pagination_page_size": {
"description": "The number of elements you want returned on each page.",
"in": "query",
"name": "page_size",
"schema": {
"default": 50,
"maximum": 100,
"minimum": 1,
"type": "integer"
}
},
"trait_pagination_page_token": {
"description": "The stats endpoints are paginated. To get the next page, call the passed `_metadata.next` URL. If `_metadata.prev` doesn't exist, you're at the first page. Similarly, if `_metadata.next` is not present, you're at the last page.",
"in": "query",
"name": "page_token",
"schema": {
"type": "string"
}
},
"trait_singlesendQueryParams2_ab_phase_id": {
"in": "query",
"name": "ab_phase_id",
"schema": {
"enum": [
"test",
"send"
],
"type": "string"
}
},
"trait_singlesendQueryParams2_ab_variation_id": {
"in": "query",
"name": "ab_variation_id",
"schema": {
"format": "uuid",
"type": "string"
}
},
"trait_singlesendQueryParams2_group_by": {
"description": "A/B Single Sends have multiple variation IDs and phase IDs. Including these additional fields allows further granularity of stats by these fields.",
"explode": false,
"in": "query",
"name": "group_by",
"schema": {
"items": {
"enum": [
"ab_variation",
"ab_phase"
],
"type": "string"
},
"type": "array"
},
"style": "form"
},
"trait_singlesendQueryParams_group_by": {
"description": "A/B Single Sends have multiple variation IDs and phase IDs. Including these additional fields allows further granularity of stats by these fields.",
"explode": false,
"in": "query",
"name": "group_by",
"schema": {
"items": {
"enum": [
"ab_variation",
"ab_phase"
],
"type": "string"
},
"type": "array"
},
"style": "form"
},
"trait_statsAdvancedQueryStringsLimitOffset_aggregated_by": {
"description": "How to group the statistics. Must be either \"day\", \"week\", or \"month\".",
"in": "query",
"name": "aggregated_by",
"required": false,
"schema": {
"enum": [
"day",
"week",
"month"
],
"type": "string"
}
},
"trait_statsAdvancedQueryStringsLimitOffset_end_date": {
"description": "The end date of the statistics to retrieve. Defaults to today. Must follow format YYYY-MM-DD.",
"in": "query",
"name": "end_date",
"required": false,
"schema": {
"type": "string"
}
},
"trait_statsAdvancedQueryStringsLimitOffset_limit": {
"description": "The number of results to return.",
"in": "query",
"name": "limit",
"required": false,
"schema": {
"type": "integer"
}
},
"trait_statsAdvancedQueryStringsLimitOffset_offset": {
"description": "The point in the list to begin retrieving results.",
"in": "query",
"name": "offset",
"required": false,
"schema": {
"type": "integer"
}
},
"trait_statsAdvancedQueryStringsLimitOffset_start_date": {
"description": "The starting date of the statistics to retrieve. Must follow format YYYY-MM-DD.",
"in": "query",
"name": "start_date",
"required": true,
"schema": {
"type": "string"
}
},
"trait_statsAdvancedStatsBaseQueryStrings_aggregated_by": {
"description": "How to group the statistics. Must be either \"day\", \"week\", or \"month\".",
"in": "query",
"name": "aggregated_by",
"required": false,
"schema": {
"enum": [
"day",
"week",
"month"
],
"type": "string"
}
},
"trait_statsAdvancedStatsBaseQueryStrings_end_date": {
"description": "The end date of the statistics to retrieve. Defaults to today. Must follow format YYYY-MM-DD.",
"in": "query",
"name": "end_date",
"required": false,
"schema": {
"type": "string"
}
},
"trait_statsAdvancedStatsBaseQueryStrings_start_date": {
"description": "The starting date of the statistics to retrieve. Must follow format YYYY-MM-DD.",
"in": "query",
"name": "start_date",
"required": true,
"schema": {
"type": "string"
}
}
},
"requestBodies": {
"DELETE_contactdb-lists-list_idBody": {
"content": {
"application/json": {
"schema": {
"nullable": true
}
}
}
},
"create-integration-request": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/create-integration-request"
}
}
}
},
"design-duplicate-input": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/design-duplicate-input"
}
}
}
},
"monitor": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/monitor"
}
}
}
},
"parse-setting": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/parse-setting"
}
}
}
},
"segment_write_v2": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/segment_write_v2"
}
}
}
},
"singlesend_request": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/singlesend_request"
}
}
}
},
"suppressions-request": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/suppressions-request"
}
}
}
},
"transactional_template_version_create": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/transactional_template_version_create"
}
}
}
},
"verified-sender-request-schema": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/verified-sender-request-schema"
}
}
}
}
},
"responses": {
"trait_apiKeysErrors_400": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"trait_apiKeysErrors_403": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"trait_apiKeysErrors_404": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"trait_cancelScheduledSendsErrors_400": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"field": {
"type": "string"
},
"help": {
"type": "object"
},
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
},
"id": {
"type": "string"
}
},
"type": "object"
}
}
},
"description": ""
},
"trait_errorResponse_400": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"trait_errors_400": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/errors-seg-v2"
}
}
},
"description": ""
},
"trait_errors_404": {
"description": ""
},
"trait_errors_500": {
"description": ""
},
"trait_globalErrors_401": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"trait_globalErrors_403": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"trait_globalErrors_404": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"trait_globalErrors_500": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"trait_mailSendErrors_400": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"trait_mailSendErrors_413": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"trait_makoErrorResponse_400": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"description": "The ID associated with the error.",
"type": "string"
},
"field": {
"description": "The field that generated the error.",
"nullable": true,
"type": "string"
},
"message": {
"description": "The error message.",
"type": "string"
},
"parameter": {
"type": "string"
}
},
"required": [
"message"
],
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"trait_makoErrorResponse_401": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"description": "The ID associated with the error.",
"type": "string"
},
"field": {
"description": "The field that generated the error.",
"nullable": true,
"type": "string"
},
"message": {
"description": "The error message.",
"type": "string"
},
"parameter": {
"type": "string"
}
},
"required": [
"message"
],
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"trait_makoErrorResponse_403": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"description": "The ID associated with the error.",
"type": "string"
},
"field": {
"description": "The field that generated the error.",
"nullable": true,
"type": "string"
},
"message": {
"description": "The error message.",
"type": "string"
},
"parameter": {
"type": "string"
}
},
"required": [
"message"
],
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"trait_makoErrorResponse_404": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"description": "The ID associated with the error.",
"type": "string"
},
"field": {
"description": "The field that generated the error.",
"nullable": true,
"type": "string"
},
"message": {
"description": "The error message.",
"type": "string"
},
"parameter": {
"type": "string"
}
},
"required": [
"message"
],
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"trait_makoErrorResponse_500": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"description": "The ID associated with the error.",
"type": "string"
},
"field": {
"description": "The field that generated the error.",
"nullable": true,
"type": "string"
},
"message": {
"description": "The error message.",
"type": "string"
},
"parameter": {
"type": "string"
}
},
"required": [
"message"
],
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"trait_pagination_200": {
"content": {
"application/json": {
"schema": {
"properties": {
"_metadata": {
"$ref": "#/components/schemas/metadata"
}
},
"type": "object"
}
}
},
"description": ""
},
"trait_singleSignOnErrorsTrait_400": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/sso-error-response"
}
}
},
"description": ""
},
"trait_singleSignOnErrorsTrait_401": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/sso-error-response"
}
}
},
"description": ""
},
"trait_singleSignOnErrorsTrait_403": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/sso-error-response"
}
}
},
"description": ""
},
"trait_singleSignOnErrorsTrait_429": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/sso-error-response"
}
}
},
"description": ""
},
"trait_singleSignOnErrorsTrait_500": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/sso-error-response"
}
}
},
"description": ""
}
},
"schemas": {
"TNE-senderID": {
"allOf": [
{
"properties": {
"id": {
"description": "The unique identifier of the sender.",
"type": "integer"
}
},
"type": "object"
},
{
"$ref": "#/components/schemas/senders-id-request-body"
},
{
"properties": {
"created_at": {
"description": "The time the sender identity was created.",
"type": "integer"
},
"locked": {
"description": "A sender identity is locked when it is associated with a campaign in the Draft, Scheduled, or In Progress state. You can't update or delete a locked sender identity.",
"type": "boolean"
},
"updated_at": {
"description": "The time the sender identity was last updated.",
"type": "integer"
},
"verified": {
"description": "Only verified sender identities can be used to send email.",
"properties": {
"reason": {
"description": "The reason for a verification failure, or null if verification succeeeded or has yet to take place.",
"nullable": true,
"type": "string"
},
"status": {
"description": "Whether the sender identity has been verified. Only verified sender identities can be used to send email.",
"type": "boolean"
}
},
"type": "object"
}
},
"type": "object"
}
],
"title": "Sender ID Response Body",
"x-stoplight": {
"id": "TNE-senderID",
"name": "Sender ID Response Body",
"public": true
}
},
"_metadata": {
"properties": {
"count": {
"minimum": 0,
"type": "integer"
},
"next": {
"format": "uri",
"type": "string"
},
"prev": {
"format": "uri",
"type": "string"
},
"self": {
"format": "uri",
"type": "string"
}
},
"title": "_metadata",
"type": "object",
"x-stoplight": {
"id": "_metadata",
"name": "_metadata",
"public": true
}
},
"abbv-message": {
"example": {
"clicks_count": 2,
"from_email": "from@test.com",
"last_event_time": "2017-10-13T18:56:21Z",
"msg_id": "abc123",
"opens_count": 1,
"status": "processed",
"subject": "anim Duis sint veniam",
"to_email": "test@test.com"
},
"properties": {
"clicks_count": {
"type": "integer"
},
"from_email": {
"type": "string"
},
"last_event_time": {
"description": "iso 8601 format",
"type": "string"
},
"msg_id": {
"type": "string"
},
"opens_count": {
"type": "integer"
},
"status": {
"enum": [
"processed",
"delivered",
"not_delivered"
],
"type": "string"
},
"subject": {
"type": "string"
},
"to_email": {
"type": "string"
}
},
"required": [
"from_email",
"msg_id",
"subject",
"to_email",
"status",
"opens_count",
"clicks_count",
"last_event_time"
],
"title": "Abbv. Message",
"type": "object"
},
"abtest_summary": {
"nullable": true,
"properties": {
"duration": {
"description": "How long the A/B Testing will last",
"type": "string"
},
"expiration_date": {
"description": "Last day to select an A/B Test Winner",
"nullable": true,
"type": "string"
},
"test_percentage": {
"description": "What percentage of your recipient will be included in your A/B testing",
"type": "integer"
},
"type": {
"description": "What differs between the A/B tests",
"enum": [
"subject",
"content"
],
"type": "string"
},
"winner_criteria": {
"description": "How the winner will be decided",
"enum": [
"open",
"click",
"manual"
],
"type": "string"
},
"winner_selected_at": {
"description": "When the winner was selected",
"nullable": true,
"type": "string"
},
"winning_template_id": {
"description": "Winner of the A/B Test",
"type": "string"
}
},
"required": [
"type",
"winner_criteria",
"test_percentage",
"duration",
"winning_template_id",
"winner_selected_at",
"expiration_date"
],
"title": "abTest_summary",
"type": "object",
"x-stoplight": {
"id": "abtest_summary",
"name": "abTest_summary",
"public": true
}
},
"advanced_stats_clicks": {
"description": "The individual events and their stats.",
"properties": {
"clicks": {
"description": "The number of links that were clicked in your emails.",
"type": "integer"
},
"unique_clicks": {
"description": "The number of unique recipients who clicked links in your emails.",
"type": "integer"
}
},
"title": "Stats: Advanced Stats with Clicks",
"type": "object",
"x-stoplight": {
"id": "advanced_stats_clicks",
"name": "Stats: Advanced Stats with Clicks",
"public": true
}
},
"advanced_stats_clicks_opens": {
"allOf": [
{
"$ref": "#/components/schemas/advanced_stats_clicks"
},
{
"$ref": "#/components/schemas/advanced_stats_opens"
}
],
"description": "The individual events and their stats.",
"title": "Stats: Advanced Stats with Clicks and Opens",
"x-stoplight": {
"id": "advanced_stats_clicks_opens",
"name": "Stats: Advanced Stats with Clicks and Opens",
"public": true
}
},
"advanced_stats_mailbox_provider": {
"allOf": [
{
"$ref": "#/components/schemas/advanced_stats_clicks_opens"
},
{
"description": "The individual events and their stats.",
"properties": {
"blocks": {
"description": "The number of emails that were not allowed to be delivered by ISPs.",
"type": "integer"
},
"bounces": {
"description": "The number of emails that bounced instead of being delivered.",
"type": "integer"
},
"deferred": {
"description": "The number of emails that temporarily could not be delivered.",
"type": "integer"
},
"delivered": {
"description": "The number of emails SendGrid was able to confirm were actually delivered to a recipient.",
"type": "integer"
},
"drops": {
"description": "The number of emails that were not delivered due to the recipient email address being on a suppression list.",
"type": "integer"
},
"processed": {
"description": "Requests from your website, application, or mail client via SMTP Relay or the Web API that SendGrid processed.",
"type": "integer"
},
"requests": {
"description": "The number of emails that were requested to be delivered.",
"type": "integer"
},
"spam_reports": {
"description": "The number of recipients who marked your email as spam.",
"type": "integer"
}
},
"type": "object"
}
],
"description": "The individual events and their stats.",
"title": "Stats: Advanced Stats for Mailbox Provider",
"x-stoplight": {
"id": "advanced_stats_mailbox_provider",
"name": "Stats: Advanced Stats for Mailbox Provider",
"public": true
}
},
"advanced_stats_opens": {
"description": "The individual events and their stats.",
"properties": {
"opens": {
"description": "The total number of times your emails were opened by recipients.",
"type": "integer"
},
"unique_opens": {
"description": "The number of unique recipients who opened your emails.",
"type": "integer"
}
},
"title": "Stats: Advanced Stats with Opens",
"type": "object",
"x-stoplight": {
"id": "advanced_stats_opens",
"name": "Stats: Advanced Stats with Opens",
"public": true
}
},
"all_segments_response": {
"properties": {
"_metadata": {
"$ref": "#/components/schemas/_metadata"
},
"contacts_count": {
"description": "Total number of contacts present in the segment",
"type": "integer"
},
"created_at": {
"description": "ISO8601 timestamp of when the object was created",
"type": "string"
},
"id": {
"description": "ID assigned to the segment when created.",
"format": "uuid",
"maxLength": 36,
"minLength": 36,
"type": "string"
},
"name": {
"description": "Name of the segment.",
"maxLength": 100,
"minLength": 1,
"type": "string"
},
"next_sample_update": {
"description": "ISO8601 timestamp of when the samples will be next updated",
"type": "string"
},
"parent_list_ids": {
"description": "The array of list ids to filter contacts on when building this segment. It allows only one such list id for now. We will support more in future",
"items": {
"type": "string"
},
"type": "array",
"uniqueItems": true
},
"query_version": {
"description": "If not set, segment contains a query for use with Segment v1 APIs. If set to '2', segment contains a SQL query for use in v2.",
"type": "string"
},
"sample_updated_at": {
"description": "ISO8601 timestamp of when the samples were last updated",
"type": "string"
},
"status": {
"$ref": "#/components/schemas/segment_status_response"
},
"updated_at": {
"description": "ISO8601 timestamp of when the object was last updated",
"type": "string"
}
},
"required": [
"id",
"name",
"contacts_count",
"created_at",
"updated_at",
"sample_updated_at",
"next_sample_update",
"parent_list_ids",
"query_version",
"status"
],
"title": "all_segments_response",
"type": "object",
"x-stoplight": {
"id": "all_segments_response",
"name": "all_segments_response",
"public": true
}
},
"api-error": {
"properties": {
"error_id": {
"type": "string"
},
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"message",
"field",
"error_id"
],
"title": "error",
"type": "object",
"x-stoplight": {
"id": "api-error",
"name": "error",
"public": true
}
},
"api-errors": {
"properties": {
"errors": {
"items": {
"$ref": "#/components/schemas/api-error"
},
"type": "array"
}
},
"title": "errors",
"type": "object",
"x-stoplight": {
"id": "api-errors",
"name": "errors",
"public": true
}
},
"api_key_name_id": {
"example": {
"api_key_id": "qfTQ6KG0QBiwWdJ0-pCLCA",
"name": "Mail Send"
},
"properties": {
"api_key_id": {
"description": "The ID of your API Key. ",
"type": "string"
},
"name": {
"description": "The name of your API Key.",
"type": "string"
}
},
"title": "API Key Name and ID",
"type": "object",
"x-stoplight": {
"id": "api_key_name_id",
"name": "API Key Name and ID",
"public": true
}
},
"api_key_name_id_scopes": {
"allOf": [
{
"properties": {
"scopes": {
"description": "The permissions this API Key has access to.",
"items": {
"type": "string"
},
"type": "array"
}
},
"type": "object"
},
{
"$ref": "#/components/schemas/api_key_name_id"
}
],
"example": {
"api_key_id": "qfTQ6KG0QBiwWdJ0-pCLCA",
"name": "Mail Send",
"scopes": [
"mail.send",
"mail.batch.create",
"mail.batch.read",
"mail.batch.update",
"mail.batch.delete",
"user.scheduled_sends.create",
"user.scheduled_sends.read",
"user.scheduled_sends.update",
"user.scheduled_sends.delete",
"sender_verification_eligible",
"sender_verification_legacy",
"2fa_required"
]
},
"title": "API Key Name, ID, and Scopes",
"x-stoplight": {
"id": "api_key_name_id_scopes",
"name": "API Key Name, ID, and Scopes",
"public": true
}
},
"authentication_domain": {
"example": {
"automatic_security": true,
"custom_spf": false,
"default": true,
"dns": {
"dkim1": {
"data": "s1._domainkey.u7.wl.sendgrid.net",
"host": "s1._domainkey.example.com",
"type": "cname",
"valid": true
},
"dkim2": {
"data": "s2._domainkey.u7.wl.sendgrid.net",
"host": "s2._domainkey.example.com",
"type": "cname",
"valid": true
},
"mail_cname": {
"data": "u7.wl.sendgrid.net",
"host": "mail.example.com",
"type": "cname",
"valid": true
}
},
"domain": "example.com",
"id": 45373692,
"ips": [
"127.0.0.1"
],
"legacy": false,
"subdomain": "sub",
"user_id": 66036447,
"username": "jdoe",
"valid": true
},
"properties": {
"automatic_security": {
"description": "Indicates if this authenticated domain uses automated security.",
"type": "boolean"
},
"custom_spf": {
"description": "Indicates whether this authenticated domain uses custom SPF.",
"type": "boolean"
},
"default": {
"description": "Indicates if this is the default authenticated domain.",
"type": "boolean"
},
"dns": {
"description": "The DNS records used to authenticate the sending domain.",
"properties": {
"dkim1": {
"description": "A DNS record.",
"properties": {
"data": {
"description": "The DNS record.",
"type": "string"
},
"host": {
"description": "The domain that this DNS record was created for.",
"type": "string"
},
"type": {
"description": "The type of DNS record.",
"type": "string"
},
"valid": {
"description": "Indicates if this is a valid DNS record.",
"type": "boolean"
}
},
"required": [
"valid",
"type",
"host",
"data"
],
"type": "object"
},
"dkim2": {
"description": "A DNS record.",
"properties": {
"data": {
"description": "The DNS record.",
"type": "string"
},
"host": {
"description": "The domain that this DNS record was created for.",
"type": "string"
},
"type": {
"description": "The type of DNS record.",
"type": "string"
},
"valid": {
"description": "Indicates if this is a valid DNS record.",
"type": "boolean"
}
},
"required": [
"valid",
"type",
"host",
"data"
],
"type": "object"
},
"mail_cname": {
"description": "The CNAME for your sending domain that points to sendgrid.net.",
"properties": {
"data": {
"description": "The CNAME record.",
"type": "string"
},
"host": {
"description": "The domain that this CNAME is created for.",
"format": "hostname",
"type": "string"
},
"type": {
"description": "The type of DNS record.",
"type": "string"
},
"valid": {
"description": "Indicates if this is a valid CNAME.",
"type": "boolean"
}
},
"required": [
"valid",
"type",
"host",
"data"
],
"type": "object"
}
},
"required": [
"mail_cname",
"dkim1",
"dkim2"
],
"type": "object"
},
"domain": {
"description": "The domain to be authenticated.",
"type": "string"
},
"id": {
"description": "The ID of the authenticated domain.",
"type": "number"
},
"ips": {
"description": "The IPs to be included in the custom SPF record for this authenticated domain.",
"items": {
"type": "string"
},
"type": "array"
},
"legacy": {
"description": "Indicates if this authenticated domain was created using the legacy whitelabel tool. If it is a legacy whitelabel, it will still function, but you'll need to create a new authenticated domain if you need to update it.",
"type": "boolean"
},
"subdomain": {
"description": "The subdomain to use for this authenticated domain.",
"type": "string"
},
"user_id": {
"description": "The ID of the user that this domain is associated with.",
"type": "number"
},
"username": {
"description": "The username that this domain will be associated with.",
"type": "string"
},
"valid": {
"description": "Indicates if this is a valid authenticated domain.",
"type": "boolean"
}
},
"required": [
"id",
"user_id",
"subdomain",
"domain",
"username",
"ips",
"custom_spf",
"default",
"legacy",
"automatic_security",
"valid",
"dns"
],
"title": "Domain Authentication - Mandatory Subdomain",
"type": "object",
"x-stoplight": {
"id": "authentication::domain",
"name": "Domain Authentication - Mandatory Subdomain",
"public": true
}
},
"automations-link-stats-response": {
"properties": {
"_metadata": {
"$ref": "#/components/schemas/link-tracking-metadata"
},
"results": {
"description": "",
"items": {
"properties": {
"clicks": {
"description": "The number of clicks on this particular link.",
"minimum": 1,
"type": "integer"
},
"step_id": {
"description": "This is the ID of the step if the stats were requested to be grouped by `step_id`.",
"format": "uuid",
"type": "string"
},
"url": {
"description": "This is the URL of the link clicked. If `{{custom_fields}}` are part of the URL, they will be included.",
"format": "uri",
"type": "string"
},
"url_location": {
"description": "This is the location of the link clicked in each Automation step. Links are located according to their position within the message; the topmost link has index `0`.",
"minimum": 0,
"type": "integer"
}
},
"required": [
"url",
"step_id",
"clicks"
],
"type": "object"
},
"type": "array"
},
"total_clicks": {
"type": "integer"
}
},
"required": [
"results",
"total_clicks",
"_metadata"
],
"title": "automations-link-stats-response",
"type": "object",
"x-stoplight": {
"id": "automations-link-stats-response",
"name": "automations-link-stats-response",
"public": true
}
},
"automations-response": {
"properties": {
"_metadata": {
"$ref": "#/components/schemas/metadata"
},
"results": {
"items": {
"properties": {
"aggregation": {
"default": "total",
"description": "This describes the time unit to which the stat is rolled up. It is based on the `aggregated_by` parameter included in the request. It can be \"total\" or the date (in YYYY-MM-DD format) the stats are for.",
"type": "string"
},
"id": {
"description": "This is the ID of the Automation you are requesting stats for.",
"format": "uuid",
"type": "string"
},
"stats": {
"$ref": "#/components/schemas/metrics"
},
"step_id": {
"default": "all",
"description": "This is the ID of the step if the stats were requested to be grouped by `step_id`.",
"type": "string"
}
},
"required": [
"id",
"aggregation",
"step_id"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"results"
],
"title": "automations-response",
"type": "object",
"x-stoplight": {
"id": "automations-response",
"name": "automations-response",
"public": true
}
},
"blocks-response": {
"example": [
{
"created": 1443651154,
"email": "example@example.com",
"reason": "error dialing remote address: dial tcp 10.57.152.165:25: no route to host",
"status": "4.0.0"
}
],
"items": {
"properties": {
"created": {
"description": "A Unix timestamp indicating when the email address was added to the blocks list.",
"type": "integer"
},
"email": {
"description": "The email address that was added to the block list.",
"format": "email",
"type": "string"
},
"reason": {
"description": "An explanation for the reason of the block.",
"type": "string"
},
"status": {
"description": "The status of the block.",
"type": "string"
}
},
"required": [
"created",
"email",
"reason",
"status"
],
"type": "object"
},
"title": "Blocks Response",
"type": "array",
"x-stoplight": {
"id": "blocks-response",
"name": "Blocks Response",
"public": true
}
},
"bounce_response": {
"example": {
"created": 1250337600,
"email": "example@example.com",
"reason": "550 5.1.1 The email account that you tried to reach does not exist. Please try double-checking the recipient's email address for typos or unnecessary spaces. Learn more at https://support.google.com/mail/answer/6596 o186si2389584ioe.63 - gsmtp ",
"status": "5.1.1"
},
"properties": {
"created": {
"description": "The unix timestamp for when the bounce record was created at SendGrid.",
"type": "number"
},
"email": {
"description": "The email address that was added to the bounce list.",
"format": "email",
"type": "string"
},
"reason": {
"description": "The reason for the bounce. This typically will be a bounce code, an enhanced code, and a description.",
"type": "string"
},
"status": {
"description": "Enhanced SMTP bounce response",
"type": "string"
}
},
"title": "Bounce Response",
"type": "object",
"x-stoplight": {
"id": "bounce_response",
"name": "Bounce Response",
"public": true
}
},
"campaign_request": {
"example": {
"categories": [
"summer line"
],
"custom_unsubscribe_url": "",
"html_content": "Check out our summer line!
",
"id": 986724,
"ip_pool": "marketing",
"list_ids": [
110,
124
],
"plain_content": "Check out our summer line!",
"segment_ids": [
110
],
"sender_id": 124451,
"status": "Draft",
"subject": "New Products for Summer!",
"suppression_group_id": 42,
"title": "May Newsletter"
},
"properties": {
"categories": {
"description": "The categories you would like associated to this campaign.",
"items": {
"type": "string"
},
"nullable": true,
"type": "array"
},
"custom_unsubscribe_url": {
"description": "This is the url of the custom unsubscribe page that you provide for customers to unsubscribe from your suppression groups.",
"nullable": true,
"type": "string"
},
"editor": {
"description": "The editor used in the UI.",
"enum": [
"code",
"design"
],
"type": "string"
},
"html_content": {
"description": "The HTML of your marketing email.",
"nullable": true,
"type": "string"
},
"ip_pool": {
"description": "The pool of IPs that you would like to send this email from.",
"nullable": true,
"type": "string"
},
"list_ids": {
"description": "The IDs of the lists you are sending this campaign to. You can have both segment IDs and list IDs",
"items": {
"type": "integer"
},
"nullable": true,
"type": "array"
},
"plain_content": {
"description": "The plain text content of your emails.",
"nullable": true,
"type": "string"
},
"segment_ids": {
"description": "The segment IDs that you are sending this list to. You can have both segment IDs and list IDs. Segments are limited to 10 segment IDs.",
"items": {
"type": "integer"
},
"nullable": true,
"type": "array"
},
"sender_id": {
"description": "The ID of the \"sender\" identity that you have created. Your recipients will see this as the \"from\" on your marketing emails.",
"nullable": true,
"type": "integer"
},
"subject": {
"description": "The subject of your campaign that your recipients will see.",
"nullable": true,
"type": "string"
},
"suppression_group_id": {
"description": "The suppression group that this marketing email belongs to, allowing recipients to opt-out of emails of this type.",
"nullable": true,
"type": "integer"
},
"title": {
"description": "The display title of your campaign. This will be viewable by you in the Marketing Campaigns UI.",
"type": "string"
}
},
"required": [
"title"
],
"title": "Campaigns Request",
"type": "object",
"x-stoplight": {
"id": "campaign_request",
"name": "Campaigns Request",
"public": true
}
},
"campaign_response": {
"allOf": [
{
"$ref": "#/components/schemas/campaign_request"
},
{
"properties": {
"id": {
"type": "integer"
},
"status": {
"description": "The status of your campaign.",
"type": "string"
}
},
"required": [
"status"
],
"type": "object"
}
],
"title": "Campaigns Response",
"x-stoplight": {
"id": "campaign_response",
"name": "Campaigns Response",
"public": true
}
},
"category_stats": {
"example": {
"date": "2015-01-01",
"stats": [
{
"metrics": {
"blocks": 0,
"bounce_drops": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"invalid_emails": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_report_drops": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0,
"unsubscribe_drops": 0,
"unsubscribes": 0
},
"name": "cat1",
"type": "category"
},
{
"metrics": {
"blocks": 0,
"bounce_drops": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"invalid_emails": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_report_drops": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0,
"unsubscribe_drops": 0,
"unsubscribes": 0
},
"name": "cat2",
"type": "category"
}
]
},
"properties": {
"date": {
"description": "The date the statistics were gathered.",
"type": "string"
},
"stats": {
"items": {
"properties": {
"metrics": {
"properties": {
"blocks": {
"description": "The number of emails that were not allowed to be delivered by ISPs.",
"type": "integer"
},
"bounce_drops": {
"description": "The number of emails that were dropped because of a bounce.",
"type": "integer"
},
"bounces": {
"description": "The number of emails that bounced instead of being delivered.",
"type": "integer"
},
"clicks": {
"description": "The number of links that were clicked.",
"type": "integer"
},
"deferred": {
"description": "The number of emails that temporarily could not be delivered.",
"type": "integer"
},
"delivered": {
"description": "The number of emails SendGrid was able to confirm were actually delivered to a recipient.",
"type": "integer"
},
"invalid_emails": {
"description": "The number of recipients who had malformed email addresses or whose mail provider reported the address as invalid.",
"type": "integer"
},
"opens": {
"description": "The total number of times your emails were opened by recipients.",
"type": "integer"
},
"processed": {
"description": "Requests from your website, application, or mail client via SMTP Relay or the API that SendGrid processed.",
"type": "integer"
},
"requests": {
"description": "The number of emails that were requested to be delivered.",
"type": "integer"
},
"spam_report_drops": {
"description": "The number of emails that were dropped due to a recipient previously marking your emails as spam.",
"type": "integer"
},
"spam_reports": {
"description": "The number of recipients who marked your email as spam.",
"type": "integer"
},
"unique_clicks": {
"description": "The number of unique recipients who clicked links in your emails.",
"type": "integer"
},
"unique_opens": {
"description": "The number of unique recipients who opened your emails.",
"type": "integer"
},
"unsubscribe_drops": {
"description": "The number of emails dropped due to a recipient unsubscribing from your emails.",
"type": "integer"
},
"unsubscribes": {
"description": "The number of recipients who unsubscribed from your emails.",
"type": "integer"
}
},
"required": [
"blocks",
"bounce_drops",
"bounces",
"clicks",
"deferred",
"delivered",
"invalid_emails",
"opens",
"processed",
"requests",
"spam_report_drops",
"spam_reports",
"unique_clicks",
"unique_opens",
"unsubscribe_drops",
"unsubscribes"
],
"type": "object"
},
"name": {
"description": "The name of the category.",
"type": "string"
},
"type": {
"description": "How you are segmenting your statistics.",
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"date"
],
"title": "Stats: Category Stats",
"type": "object",
"x-stoplight": {
"id": "category_stats",
"name": "Stats: Category Stats",
"public": true
}
},
"cc_bcc_email_object": {
"example": {
"email": "jane_doe@example.com",
"name": "Jane Doe"
},
"properties": {
"email": {
"description": "The intended recipient's email address.",
"format": "email",
"type": "string"
},
"name": {
"description": "The intended recipient's name.",
"type": "string"
}
},
"required": [
"email"
],
"title": "CC BCC Email Object",
"type": "object",
"x-stoplight": {
"id": "cc_bcc_email_object",
"name": "CC BCC Email Object",
"public": true
}
},
"click-tracking": {
"example": {
"enable_text": false,
"enabled": false
},
"properties": {
"enable_text": {
"description": "Indicates if click tracking is enabled for plain text emails.",
"type": "boolean"
},
"enabled": {
"description": "Indicates if click tracking is enabled or disabled.",
"type": "boolean"
}
},
"required": [
"enable_text",
"enabled"
],
"title": "Settings: Click Tracking",
"type": "object",
"x-stoplight": {
"id": "click-tracking",
"name": "Settings: Click Tracking",
"public": true
}
},
"contact-details": {
"properties": {
"_metadata": {
"$ref": "#/components/schemas/selfmetadata"
},
"address_line_1": {
"type": "string"
},
"address_line_2": {
"type": "string"
},
"alternate_emails": {
"items": {
"type": "string"
},
"type": "array"
},
"city": {
"type": "string"
},
"country": {
"type": "string"
},
"created_at": {
"description": "The ISO8601 timestamp when the contact was created.",
"type": "string"
},
"custom_fields": {
"$ref": "#/components/schemas/custom-fields-by-name"
},
"email": {
"type": "string"
},
"first_name": {
"type": "string"
},
"id": {
"type": "string"
},
"last_name": {
"type": "string"
},
"list_ids": {
"items": {
"type": "string"
},
"type": "array"
},
"postal_code": {
"type": "string"
},
"state_province_region": {
"type": "string"
},
"updated_at": {
"description": "The ISO8601 timestamp when the contact was updated.",
"type": "string"
}
},
"required": [
"id",
"list_ids",
"created_at",
"updated_at"
],
"title": "contact-details",
"type": "object",
"x-stoplight": {
"id": "contact-details",
"name": "contact-details",
"public": true
}
},
"contact-details2": {
"properties": {
"_metadata": {
"$ref": "#/components/schemas/selfmetadata"
},
"address_line_1": {
"type": "string"
},
"address_line_2": {
"type": "string"
},
"alternate_emails": {
"items": {
"format": "email",
"type": "string"
},
"nullable": true,
"type": "array"
},
"city": {
"type": "string"
},
"country": {
"type": "string"
},
"created_at": {
"format": "date-time",
"type": "string"
},
"custom_fields": {
"type": "object"
},
"email": {
"format": "email",
"type": "string"
},
"facebook": {
"type": "string"
},
"first_name": {
"type": "string"
},
"id": {
"format": "uuid",
"maxLength": 36,
"minLength": 36,
"type": "string"
},
"last_name": {
"type": "string"
},
"line": {
"type": "string"
},
"list_ids": {
"items": {
"format": "uuid",
"type": "string"
},
"type": "array"
},
"phone_number": {
"type": "string"
},
"postal_code": {
"type": "string"
},
"segment_ids": {
"items": {
"format": "uuid",
"type": "string"
},
"type": "array"
},
"state_province_region": {
"type": "string"
},
"unique_name": {
"type": "string"
},
"updated_at": {
"format": "date-time",
"type": "string"
},
"whatsapp": {
"type": "string"
}
},
"required": [
"id",
"list_ids",
"created_at",
"updated_at"
],
"title": "contact-details2",
"type": "object",
"x-stoplight": {
"id": "contact-details2",
"name": "contact-details2",
"public": true
}
},
"contact-details3": {
"properties": {
"_metadata": {
"$ref": "#/components/schemas/selfmetadata"
},
"address_line_1": {
"type": "string"
},
"address_line_2": {
"type": "string"
},
"alternate_emails": {
"items": {
"type": "string"
},
"type": "array"
},
"city": {
"type": "string"
},
"country": {
"type": "string"
},
"created_at": {
"type": "string"
},
"custom_fields": {
"type": "object"
},
"email": {
"type": "string"
},
"facebook": {
"type": "string"
},
"first_name": {
"type": "string"
},
"id": {
"type": "string"
},
"last_name": {
"type": "string"
},
"line": {
"type": "string"
},
"list_ids": {
"items": {
"type": "string"
},
"type": "array"
},
"phone_number": {
"type": "string"
},
"postal_code": {
"type": "string"
},
"segment_ids": {
"items": {
"type": "string"
},
"type": "array"
},
"state_province_region": {
"type": "string"
},
"unique_name": {
"type": "string"
},
"updated_at": {
"type": "string"
},
"whatsapp": {
"type": "string"
}
},
"required": [
"id",
"list_ids",
"segment_ids",
"created_at",
"updated_at"
],
"title": "contact-details3",
"type": "object",
"x-stoplight": {
"id": "contact-details3",
"name": "contact-details3",
"public": true
}
},
"contact-export": {
"properties": {
"_metadata": {
"$ref": "#/components/schemas/metadata"
},
"completed_at": {
"description": "The ISO8601 timestamp when the export was completed.",
"type": "string"
},
"contact_count": {
"description": "The total number of exported contacts.",
"type": "integer"
},
"created_at": {
"description": "The ISO8601 timestamp when the export was begun.",
"type": "string"
},
"expires_at": {
"description": "The ISO8601 timestamp when the exported file on S3 will expire.",
"type": "string"
},
"id": {
"type": "string"
},
"message": {
"description": "A human readable message if the status is `failure`.",
"type": "string"
},
"status": {
"description": "The export job's status. Allowed values: `pending`, `ready`, or `failure`.",
"enum": [
"pending",
"ready",
"failure"
],
"type": "string"
},
"updated_at": {
"description": "The ISO8601 timestamp when the export was updated.",
"type": "string"
},
"urls": {
"description": "One or more download URLs for the contact file if the status is `ready`.",
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"id",
"status",
"created_at",
"updated_at",
"expires_at"
],
"title": "contact-export",
"type": "object",
"x-stoplight": {
"id": "contact-export",
"name": "contact-export",
"public": true
}
},
"contact-import": {
"properties": {
"finished_at": {
"description": "The ISO8601 timestamp when the job was finished.",
"type": "string"
},
"id": {
"description": "The job ID.",
"type": "string"
},
"job_type": {
"description": "The job type. Allowed values: `upsert`, or `delete`.",
"type": "string"
},
"results": {
"description": "Result map of the import job.",
"properties": {
"created_count": {
"description": "Created contact count from the import.",
"type": "number"
},
"deleted_count": {
"description": "Count of deleted contacts that resulted in error.",
"type": "number"
},
"errored_count": {
"description": "Count of imported contacts that resulted in error.",
"type": "number"
},
"errors_url": {
"description": "The download URL of the file which provides information about any errors.",
"type": "string"
},
"requested_count": {
"description": "Requested contact count from the import.",
"type": "number"
},
"updated_count": {
"description": "Updated contact count from the import.",
"type": "number"
}
},
"type": "object"
},
"started_at": {
"description": "The ISO8601 timestamp when the job was created.",
"type": "string"
},
"status": {
"description": "The job state. Allowed values: `pending`, `completed`, `errored`, or `failed`.",
"type": "string"
}
},
"title": "contact-import",
"type": "object",
"x-stoplight": {
"id": "contact-import",
"name": "contact-import",
"public": true
}
},
"contact-request": {
"properties": {
"address_line_1": {
"description": "The first line of the address.",
"maxLength": 100,
"type": "string"
},
"address_line_2": {
"description": "An optional second line for the address.",
"maxLength": 100,
"type": "string"
},
"alternate_emails": {
"description": "Additional emails associated with the contact.",
"items": {
"maxLength": 254,
"type": "string"
},
"maxItems": 5,
"minItems": 0,
"type": "array"
},
"city": {
"description": "The contact's city.",
"maxLength": 60,
"type": "string"
},
"country": {
"description": "The contact's country. Can be a full name or an abbreviation.",
"maxLength": 50,
"type": "string"
},
"custom_fields": {
"$ref": "#/components/schemas/custom-fields-by-id"
},
"email": {
"description": "The contact's primary email. This is required to be a valid email.",
"maxLength": 254,
"type": "string"
},
"first_name": {
"description": "The contact's personal name.",
"maxLength": 50,
"type": "string"
},
"last_name": {
"description": "The contact's family name.",
"maxLength": 50,
"type": "string"
},
"postal_code": {
"description": "The contact's ZIP code or other postal code.",
"type": "string"
},
"state_province_region": {
"description": "The contact's state, province, or region.",
"maxLength": 50,
"type": "string"
}
},
"required": [
"email"
],
"title": "contact-request",
"type": "object",
"x-stoplight": {
"id": "contact-request",
"name": "contact-request",
"public": true
}
},
"contact-summary": {
"properties": {
"_metadata": {
"$ref": "#/components/schemas/selfmetadata"
},
"created_at": {
"description": "Unix Epoch Timestamp.",
"type": "number"
},
"email": {
"description": "Primary email address.",
"type": "string"
},
"first_name": {
"type": "string"
},
"id": {
"description": "Contact UUID.",
"type": "string"
},
"last_name": {
"type": "string"
},
"list_ids": {
"description": "List UUID linked with this contact.",
"items": {
"type": "string"
},
"type": "array"
},
"updated_at": {
"description": "Unix Epoch Timestamp.",
"type": "number"
}
},
"required": [
"id",
"list_ids",
"created_at",
"updated_at"
],
"title": "contact-summary",
"type": "object",
"x-stoplight": {
"id": "contact-summary",
"name": "contact-summary",
"public": true
}
},
"contact_response": {
"example": {
"address_line_1": "street address / P.O. box / CompanyName / c/o",
"address_line_2": "apartment, suite, unit, building, floor etc",
"alternate_emails": [
"abcd@yahoo.com",
"abcd@hotmail.com"
],
"city": "Redwood City",
"country": "USA",
"custom_fields": {
"custom_field_name1": "custom_field_value1",
"custom_field_name2": "custom_field_value2"
},
"email": "abcd@gmail.com",
"first_name": "Ab",
"id": "47d23ab0-d895-4359-a0d1-ffc7a6fc7e70",
"last_name": "Cd",
"postal_code": 94063,
"state_province_region": "CA"
},
"properties": {
"address_line_1": {
"description": "First line of address of the contact. This is a reserved field.",
"minLength": 0,
"type": "string"
},
"address_line_2": {
"description": "Second line of address of the contact. This is a reserved field.",
"minLength": 0,
"type": "string"
},
"alternate_emails": {
"description": "Alternate emails of the contact. This is a reserved field.",
"items": {
"format": "email",
"maxLength": 254,
"minLength": 3,
"type": "string"
},
"minItems": 0,
"type": "array",
"uniqueItems": true
},
"city": {
"description": "City associated with the contact. This is a reserved field.",
"minLength": 0,
"pattern": "^[a-zA-Z\\u0080-\\u024F\\s\\/\\-\\)\\(\\`\\.\\\"\\']+$",
"type": "string"
},
"country": {
"description": "Country associated with the address of the contact. This is a reserved field.",
"minLength": 0,
"type": "string"
},
"custom_fields": {
"description": "The user may choose to create up to 120 custom fields or none at all. This is not a reserved field.",
"minProperties": 0,
"properties": {
"": {
"type": "string"
},
"custom_field_name1": {
"minLength": 0,
"type": "string"
},
"custom_field_name2": {
"minLength": 0,
"type": "string"
}
},
"type": "object"
},
"email": {
"description": "Email of the contact. This is a reserved field.",
"format": "email",
"maxLength": 254,
"minLength": 3,
"type": "string"
},
"first_name": {
"description": "First name of the contact. This is a reserved field.",
"minLength": 1,
"type": "string"
},
"id": {
"description": "ID assigned to a contact when added to the system.",
"format": "uuid",
"maxLength": 36,
"pattern": "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}",
"type": "string"
},
"last_name": {
"description": "Last name of the contact. This is a reserved field.",
"minLength": 1,
"type": "string"
},
"list_ids": {
"description": "IDs of all lists the contact is part of",
"items": {
"format": "uuid",
"type": "string"
},
"type": "array",
"uniqueItems": true
},
"postal_code": {
"description": "Zipcode associated with the address of the contact. This is a reserved field.",
"type": "integer"
},
"segment_ids": {
"description": "IDs of all segments the contact is part of",
"items": {
"format": "uuid",
"type": "string"
},
"type": "array",
"uniqueItems": true
},
"state_province_region": {
"description": "State associated with the contact. This is a reserved field.",
"minLength": 0,
"type": "string"
}
},
"required": [
"id",
"email",
"alternate_emails",
"first_name",
"last_name",
"address_line_1",
"address_line_2",
"city",
"state_province_region",
"postal_code",
"country",
"custom_fields"
],
"title": "contact_response",
"type": "object",
"x-stoplight": {
"id": "contact_response",
"name": "contact_response",
"public": true
}
},
"contactdb_custom_field": {
"example": {
"name": "first_name",
"type": "text"
},
"properties": {
"name": {
"description": "The name of the field",
"type": "string"
},
"type": {
"description": "The type of the field.",
"enum": [
"date",
"text",
"number"
],
"type": "string"
}
},
"title": "ContactDB Custom field schema.",
"type": "object",
"x-stoplight": {
"id": "contactdb_custom_field",
"name": "ContactDB: Custom Field",
"public": true
}
},
"contactdb_custom_field_with_id": {
"allOf": [
{
"$ref": "#/components/schemas/contactdb_custom_field"
},
{
"properties": {
"id": {
"description": "The ID of the custom field.",
"type": "number"
}
},
"type": "object"
}
],
"title": "ContactDB Custom field schema with ID.",
"x-stoplight": {
"id": "contactdb_custom_field_with_id",
"name": "ContactDB: Custom Field with ID",
"public": true
}
},
"contactdb_custom_field_with_id_value": {
"allOf": [
{
"$ref": "#/components/schemas/contactdb_custom_field_with_id"
},
{
"properties": {
"value": {
"description": "The value of this recipient's custom field",
"nullable": true,
"type": "string"
}
},
"type": "object"
}
],
"title": "ContactDB Custom field schema.",
"x-stoplight": {
"id": "contactdb_custom_field_with_id_value",
"name": "ContactDB: Custom Field with ID & Value",
"public": true
}
},
"contactdb_list": {
"example": {
"id": 1,
"name": "listname",
"recipient_count": 0
},
"properties": {
"id": {
"description": "The reference ID of your list.",
"type": "integer"
},
"name": {
"description": "The name of your list. Must be unique against all other list and segment names.",
"type": "string"
},
"recipient_count": {
"description": "The count of recipients currently in the list.",
"type": "integer"
}
},
"required": [
"id",
"name",
"recipient_count"
],
"title": "ContactDB lists",
"type": "object",
"x-stoplight": {
"id": "contactdb_list",
"name": "ContactDB: List",
"public": true
}
},
"contactdb_recipient": {
"properties": {
"recipients": {
"items": {
"properties": {
"created_at": {
"description": "The time this record was created in your contactdb, in unixtime.",
"type": "number"
},
"custom_fields": {
"description": "The custom fields assigned to this recipient and their values.",
"items": {
"$ref": "#/components/schemas/contactdb_custom_field_with_id_value"
},
"type": "array"
},
"email": {
"description": "The email address of this recipient. This is a default custom field that SendGrid provides.",
"format": "email",
"type": "string"
},
"first_name": {
"description": "The first name of this recipient. This is a default custom field that SendGrid provides.",
"nullable": true,
"type": "string"
},
"id": {
"description": "The ID of this recipient.",
"type": "string"
},
"last_clicked": {
"description": "The last time this recipient clicked a link from one of your campaigns, in unixtime.",
"nullable": true,
"type": "number"
},
"last_emailed": {
"description": "The last time this user was emailed by one of your campaigns, in unixtime.",
"nullable": true,
"type": "number"
},
"last_name": {
"description": "The last name of the recipient.",
"nullable": true,
"type": "string"
},
"last_opened": {
"description": "The last time this recipient opened an email from you, in unixtime.",
"nullable": true,
"type": "number"
},
"updated_at": {
"description": "The last update date for this recipient's record.",
"type": "number"
}
},
"required": [
"email"
],
"type": "object"
},
"type": "array"
}
},
"title": "ContactDB: Recipient",
"type": "object",
"x-stoplight": {
"id": "contactdb_recipient",
"name": "ContactDB: Recipient",
"public": true
}
},
"contactdb_recipient_count": {
"example": {
"recipient_count": 1234
},
"properties": {
"recipient_count": {
"description": "The count of recipients.",
"type": "number"
}
},
"required": [
"recipient_count"
],
"title": "ContactDB: Recipient Count",
"type": "object",
"x-stoplight": {
"id": "contactdb_recipient_count",
"name": "ContactDB: Recipient Count",
"public": true
}
},
"contactdb_recipient_response": {
"example": {
"error_count": 1,
"error_indices": [
2
],
"errors": [
{
"error_indices": [
2
],
"message": "Invalid email."
}
],
"new_count": 2,
"persisted_recipients": [
"YUBh",
"bWlsbGVyQG1pbGxlci50ZXN0"
],
"updated_count": 0
},
"properties": {
"error_count": {
"default": 0,
"description": "The number of errors found while adding recipients.",
"type": "number"
},
"error_indices": {
"default": [],
"description": "The indices of the recipient(s) sent that caused the error. ",
"items": {
"type": "number"
},
"type": "array"
},
"errors": {
"items": {
"properties": {
"error_indices": {
"items": {
"type": "number"
},
"type": "array"
},
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
},
"new_count": {
"default": 0,
"description": "The count of new recipients added to the contactdb.",
"type": "number"
},
"persisted_recipients": {
"default": [],
"description": "The recipient IDs of the recipients that already existed from this request.",
"items": {
"type": "string"
},
"type": "array"
},
"updated_count": {
"default": 0,
"description": "The recipients who were updated from this request.",
"type": "number"
}
},
"required": [
"error_count",
"new_count",
"persisted_recipients",
"updated_count"
],
"title": "ContactDB: Recipient response",
"type": "object",
"x-stoplight": {
"id": "contactdb_recipient_response",
"name": "ContactDB: Recipient response",
"public": true
}
},
"contactdb_segments": {
"example": {
"conditions": [
{
"and_or": "",
"field": "last_name",
"operator": "eq",
"value": "Miller"
},
{
"and_or": "and",
"field": "last_clicked",
"operator": "gt",
"value": "01/02/2015"
},
{
"and_or": "or",
"field": "clicks.campaign_identifier",
"operator": "eq",
"value": "513"
}
],
"list_id": 4,
"name": "Last Name Miller",
"recipient_count": 1234
},
"properties": {
"conditions": {
"description": "The conditions for a recipient to be included in this segment.",
"items": {
"$ref": "#/components/schemas/contactdb_segments_conditions"
},
"type": "array"
},
"list_id": {
"description": "The list id from which to make this segment. Not including this ID will mean your segment is created from the main contactdb rather than a list.",
"type": "integer"
},
"name": {
"description": "The name of this segment.",
"type": "string"
},
"recipient_count": {
"description": "The count of recipients in this list. This is not included on creation of segments.",
"type": "number"
}
},
"required": [
"name",
"conditions"
],
"title": "Create a Segment request",
"type": "object",
"x-stoplight": {
"id": "contactdb_segments",
"name": "ContactDB: Segments",
"public": true
}
},
"contactdb_segments_conditions": {
"properties": {
"and_or": {
"enum": [
"and",
"or",
""
],
"type": "string"
},
"field": {
"type": "string"
},
"operator": {
"enum": [
"eq",
"ne",
"lt",
"gt",
"contains"
],
"type": "string"
},
"value": {
"type": "string"
}
},
"required": [
"field",
"value",
"operator"
],
"title": "ContactDB: Segments: Conditions",
"type": "object",
"x-stoplight": {
"id": "contactdb_segments_conditions",
"name": "ContactDB: Segments: Conditions",
"public": true
}
},
"contactdb_segments_with_id": {
"allOf": [
{
"properties": {
"id": {
"description": "The ID of the segment.",
"type": "number"
}
},
"required": [
"id"
],
"type": "object"
},
{
"$ref": "#/components/schemas/contactdb_segments"
}
],
"title": "ContactDB:: Segments with ID",
"x-stoplight": {
"id": "contactdb_segments_with_id",
"name": "ContactDB:: Segments with ID",
"public": true
}
},
"contacts": {
"properties": {
"address": {
"type": "string"
},
"address2": {
"type": "object"
},
"city": {
"type": "string"
},
"company": {
"type": "string"
},
"country": {
"type": "string"
},
"email": {
"type": "string"
},
"first_name": {
"type": "string"
},
"last_name": {
"type": "string"
},
"phone": {
"type": "string"
},
"state": {
"type": "string"
},
"zip": {
"type": "string"
}
},
"title": "Contacts",
"type": "object",
"x-stoplight": {
"id": "contacts",
"name": "Contacts",
"public": true
}
},
"create-integration-request": {
"properties": {
"completed_integration": {
"description": "Indicates if the integration is complete.",
"type": "boolean"
},
"enabled": {
"description": "Indicates if the integration is enabled.",
"type": "boolean"
},
"entity_id": {
"description": "An identifier provided by your IdP to identify Twilio SendGrid in the SAML interaction. This is called the \"SAML Issuer ID\" in the Twilio SendGrid UI.",
"type": "string"
},
"name": {
"description": "The name of your integration. This name can be anything that makes sense for your organization (eg. Twilio SendGrid)",
"type": "string"
},
"signin_url": {
"description": "The IdP's SAML POST endpoint. This endpoint should receive requests and initiate an SSO login flow. This is called the \"Embed Link\" in the Twilio SendGrid UI.",
"type": "string"
},
"signout_url": {
"description": "This URL is relevant only for an IdP-initiated authentication flow. If a user authenticates from their IdP, this URL will return them to their IdP when logging out.",
"type": "string"
}
},
"required": [
"name",
"enabled",
"signin_url",
"signout_url",
"entity_id"
],
"title": "Create Integration Request",
"type": "object",
"x-stoplight": {
"id": "create-integration-request",
"name": "Create Integration Request",
"public": true
}
},
"credentials": {
"example": {
"address": "1234 example street",
"address2": null,
"city": "Denver",
"company": "Company name",
"country": "US",
"email": "example@example.com",
"first_name": "Example",
"last_name": "User",
"phone": "(555) 555-5555",
"state": "CO",
"zip": "55555"
},
"properties": {
"permissions": {
"properties": {
"api": {
"type": "string"
},
"mail": {
"type": "string"
},
"web": {
"type": "string"
}
},
"type": "object"
},
"username": {
"type": "string"
}
},
"title": "Credentials",
"type": "object",
"x-stoplight": {
"id": "credentials",
"name": "Credentials",
"public": true
}
},
"custom-fields-by-id": {
"example": {
"e2": "Coffee is a beverage that puts one to sleep when not drank.",
"w1": "2002-10-02T15:00:00Z",
"w33": 9.5
},
"title": "custom-fields-by-id",
"type": "object",
"x-stoplight": {
"id": "custom-fields-by-id",
"name": "custom-fields-by-id",
"public": true
}
},
"custom-fields-by-name": {
"example": {
"birthday": "2002-10-02T15:00:00Z",
"favoriteQuote": "Coffee is a beverage that puts one to sleep when not drank.",
"shoe_size": 9.5
},
"title": "custom-fields-by-name",
"type": "object",
"x-stoplight": {
"id": "custom-fields-by-name",
"name": "custom-fields-by-name",
"public": true
}
},
"custom_field_definitions_response": {
"example": {
"field_type": "Date",
"id": "a1_D",
"name": "custom_field_name"
},
"properties": {
"field_type": {
"enum": [
"Text",
"Number",
"Date"
],
"type": "string"
},
"id": {
"type": "string"
},
"name": {
"maxLength": 100,
"minLength": 1,
"type": "string"
}
},
"required": [
"id",
"name",
"field_type"
],
"title": "custom_field_definitions_response",
"type": "object",
"x-stoplight": {
"id": "custom_field_definitions_response",
"name": "custom_field_definitions_response",
"public": true
}
},
"design-common-fields": {
"allOf": [
{
"$ref": "#/components/schemas/design-duplicate-input"
},
{
"properties": {
"categories": {
"description": "The list of categories applied to the design",
"items": {
"maxLength": 255,
"type": "string"
},
"maxItems": 10,
"type": "array",
"uniqueItems": true
},
"generate_plain_content": {
"default": true,
"description": "If true, plain_content is always generated from html_content. If false, plain_content is not altered.",
"type": "boolean"
},
"subject": {
"description": "Subject of the Design.",
"maxLength": 5000,
"type": "string"
}
},
"type": "object"
}
],
"title": "Design Common Fields",
"x-stoplight": {
"id": "design-common-fields",
"name": "Design Common Fields",
"public": true
}
},
"design-duplicate-input": {
"example": {
"editor": "design",
"name": "Ahoy, Cake or Pie Cafe!"
},
"properties": {
"editor": {
"description": "The editor used in the UI.",
"enum": [
"code",
"design"
],
"type": "string"
},
"name": {
"default": "Duplicate: ",
"description": "The name of the new design.",
"type": "string"
}
},
"title": "Design Duplicate Design Input",
"type": "object",
"x-stoplight": {
"id": "design-duplicate-input",
"name": "Design Duplicate Design Input",
"public": true
}
},
"design-input": {
"allOf": [
{
"$ref": "#/components/schemas/design-duplicate-input"
},
{
"$ref": "#/components/schemas/design-common-fields"
},
{
"properties": {
"html_content": {
"description": "The HTML content of the Design.",
"maxLength": 1048576,
"type": "string"
},
"plain_content": {
"default": "",
"description": "Plain text content of the Design.",
"maxLength": 1048576,
"type": "string"
}
},
"required": [
"html_content"
],
"type": "object"
}
],
"example": {
"editor": "design",
"html_content": "\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ",
"name": "Ahoy, World!",
"plain_content": "Ahoy, World!\n\n{{Sender_Name}}\n\n{{Sender_Address}} , {{Sender_City}} , {{Sender_State}} {{Sender_Zip}}\n\nUnsubscribe ( {{{unsubscribe}}} ) - Unsubscribe Preferences ( {{{unsubscribe_preferences}}} )",
"subject": "Getting Started"
},
"title": "Design Input",
"x-stoplight": {
"id": "design-input",
"name": "Design Input",
"public": true
}
},
"design-output": {
"allOf": [
{
"$ref": "#/components/schemas/design-output-summary"
},
{
"$ref": "#/components/schemas/design-input"
}
],
"title": "Design Output",
"x-stoplight": {
"id": "design-output",
"name": "Design Output",
"public": true
}
},
"design-output-summary": {
"allOf": [
{
"properties": {
"created_at": {
"description": "Datetime that Design was created.",
"format": "ISO 8601 date-time",
"type": "string"
},
"id": {
"description": "ID of the Design.",
"format": "uuid",
"type": "string"
},
"thumbnail_url": {
"description": "A Thumbnail preview of the template's html content.",
"type": "string"
},
"updated_at": {
"description": "Datetime that Design was last updated.",
"format": "ISO 8601 date-time",
"type": "string"
}
},
"type": "object"
},
{
"$ref": "#/components/schemas/design-duplicate-input"
},
{
"$ref": "#/components/schemas/design-common-fields"
}
],
"example": {
"_metadata": {
"count": 3,
"self": "https://api.sendgrid.com/v3/designs?page_token=vHdvHCg0w1F-TmWJcPNpTEnFY2aPEmRBHONwOgZ6TgJbX2_I"
},
"result": [
{
"categories": [
"welcome",
"new customer"
],
"created_at": "2021-04-09T17:29:46Z",
"editor": "code",
"generate_plain_content": true,
"id": "3247eaea-c912-42a3-b0bc-60bdaf210396",
"name": "Welcome Email",
"subject": "Welcom to the Cake or Pie Cafe",
"thumbnail_url": "//us-east-2-production-thumbnail-bucket.s3.amazonaws.com/llny8o5b3m636z92p7hbjnmq1jvpka39p370jwtin2s1wxv7x1sgm0y5fk518d0s.png",
"updated_at": "2021-04-09T17:29:55Z"
},
{
"categories": [
"promo",
"coupon"
],
"created_at": "2021-04-09T17:29:21Z",
"editor": "design",
"generate_plain_content": true,
"id": "02dfd792-f31f-439a-a79e-5c47fbcfdbac",
"name": "Monthly Promo",
"subject": "Free Dozen Cupcakes",
"thumbnail_url": "//us-east-2-production-thumbnail-bucket.s3.amazonaws.com/hfyxahd7ues2ajuoeoqq2xe6ibdasl1q89ox0y9ncya2ftpoicssmtf9ddus4c39.png",
"updated_at": "2021-04-09T17:29:42Z"
},
{
"categories": [],
"created_at": "2020-10-09T17:33:46Z",
"editor": "design",
"generate_plain_content": true,
"id": "e54be823-19ef-4c6f-8b60-efd7514f492d",
"name": "Duplicate: Ingrid & Anders",
"subject": "Welcome to Ingrid & Anders!",
"thumbnail_url": "//us-east-2-production-thumbnail-bucket.s3.amazonaws.com/12kni9gjpyb9uxmwr9vk7unycjr70u95zoyhe9sg2zounul2zg7dih1s20k13q2z.png",
"updated_at": "2021-04-07T19:57:52Z"
}
]
},
"title": "Design Output - Summary",
"x-stoplight": {
"id": "design-output-summary",
"name": "Design Output - Summary",
"public": true
}
},
"domain-authentication-200-response": {
"example": [
{
"automatic_security": true,
"custom_spf": true,
"default": true,
"dns": {
"dkim1": {
"data": "s1._domainkey.u7.wl.sendgrid.net",
"host": "s1._domainkey.example.com",
"type": "cname",
"valid": true
},
"dkim2": {
"data": "s2._domainkey.u7.wl.sendgrid.net",
"host": "s2._domainkey.example.com",
"type": "cname",
"valid": true
},
"mail_cname": {
"data": "u7.wl.sendgrid.net",
"host": "mail.example.com",
"type": "cname",
"valid": true
}
},
"domain": "example.com",
"id": 1,
"ips": [
"192.168.1.1",
"192.168.1.2"
],
"legacy": false,
"subdomain": "mail",
"user_id": 7,
"username": "jane@example.com",
"valid": true
},
{
"automatic_security": true,
"custom_spf": false,
"default": true,
"dns": {
"dkim1": {
"data": "k=rsa; t=s; p=publicKey",
"host": "example2.com",
"type": "txt",
"valid": false
},
"dkim2": {
"data": "k=rsa; t=s p=publicKey",
"host": "example2.com",
"type": "txt",
"valid": false
},
"mail_cname": {
"data": "sendgrid.net",
"host": "news.example2.com",
"type": "mx",
"valid": false
}
},
"domain": "example2.com",
"id": 2,
"ips": [],
"legacy": false,
"subdomain": "new",
"user_id": 8,
"username": "john@example2.com",
"valid": false
}
],
"items": {
"allOf": [
{
"$ref": "#/components/schemas/authentication_domain"
},
{
"properties": {
"last_validation_attempt_at": {
"description": "A Unix epoch timestamp representing the last time of a validation attempt.",
"type": "integer"
},
"subusers": {
"items": {
"properties": {
"user_id": {
"description": "The ID of the subuser that this authenticated domain will be associated with.",
"type": "integer"
},
"username": {
"description": "The username of the subuser that this authenticated domain is associated with.",
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
]
},
"title": "Domain Authentication 200 Response",
"type": "array",
"x-stoplight": {
"id": "domain-authentication-200-response",
"name": "Domain Authentication 200 Response",
"public": true
}
},
"domain_authentication_domain_spf": {
"properties": {
"automatic_security": {
"description": "Indicates if this authenticated domain uses automated security.",
"type": "boolean"
},
"custom_spf": {
"description": "Indicates if this authenticated domain uses custom SPF.",
"type": "boolean"
},
"default": {
"description": "Indicates if this is the default domain.",
"type": "boolean"
},
"dns": {
"description": "The DNS records for this authenticated domain.",
"properties": {
"dkim": {
"description": "The DKIM record for messages sent using this authenticated domain.",
"properties": {
"data": {
"description": "The DKIM record.",
"type": "string"
},
"host": {
"description": "The DNS labels for the DKIM signature.",
"type": "string"
},
"type": {
"description": "The type of data in the DKIM record.",
"type": "string"
},
"valid": {
"description": "Indicates if the DKIM record is valid.",
"type": "boolean"
}
},
"required": [
"host",
"type",
"data",
"valid"
],
"type": "object"
},
"domain_spf": {
"description": "The SPF record for the root domain.",
"properties": {
"data": {
"description": "The SPF record.",
"type": "string"
},
"host": {
"description": "The root domain that this SPF record will be used to authenticate.",
"type": "string"
},
"type": {
"description": "The type of data in the SPF record.",
"type": "string"
},
"valid": {
"description": "Indicates if the SPF record is valid.",
"type": "boolean"
}
},
"required": [
"host",
"type",
"data",
"valid"
],
"type": "object"
},
"mail_server": {
"description": "Designates which mail server is responsible for accepting messages from a domain.",
"properties": {
"data": {
"description": "The mail server responsible for accepting messages from the sending domain.",
"type": "string"
},
"host": {
"description": "The domain sending the messages.",
"type": "string"
},
"type": {
"description": "They type of DNS record.",
"type": "string"
},
"valid": {
"description": "Indicates if this is a valid DNS record.",
"type": "boolean"
}
},
"required": [
"host",
"type",
"data",
"valid"
],
"type": "object"
},
"subdomain_spf": {
"description": "The SPF record for the subdomain used to create this authenticated domain.",
"properties": {
"data": {
"description": "The SPF record.",
"type": "string"
},
"host": {
"description": "The domain that this SPF record will be used to authenticate.",
"type": "string"
},
"type": {
"description": "The type of data in the SPF record.",
"type": "string"
},
"valid": {
"description": "Indicates if this is a valid SPF record.",
"type": "boolean"
}
},
"required": [
"host",
"type",
"data",
"valid"
],
"type": "object"
}
},
"required": [
"mail_server",
"subdomain_spf",
"domain_spf",
"dkim"
],
"type": "object"
},
"domain": {
"description": "The domain authenticated.",
"type": "string"
},
"id": {
"description": "The ID of the authenticated domain.",
"type": "integer"
},
"ips": {
"description": "The IP addresses that are included in the SPF record for this authenticated domain.",
"items": {},
"type": "array"
},
"legacy": {
"description": "Indicates if this authenticated domain was created using the legacy whitelabel tool. If it is a legacy whitelabel, it will still function, but you'll need to create a new authenticated domain if you need to update it.",
"type": "boolean"
},
"subdomain": {
"description": "The subdomain that was used to create this authenticated domain.",
"type": "string"
},
"user_id": {
"description": "The user_id of the account that this authenticated domain is associated with.",
"type": "integer"
},
"username": {
"description": "The username of the account that this authenticated domain is associated with.",
"type": "string"
},
"valid": {
"description": "Indicates if this is a valid authenticated domain .",
"type": "boolean"
}
},
"required": [
"id",
"domain",
"username",
"user_id",
"ips",
"custom_spf",
"default",
"legacy",
"automatic_security",
"valid",
"dns"
],
"title": "Domain Authentication",
"type": "object",
"x-stoplight": {
"id": "domain_authentication:domain_spf",
"name": "Domain Authentication",
"public": true
}
},
"email-activity-response-common-fields": {
"properties": {
"from_email": {
"default": "test0@example.com",
"description": "The 'From' email address used to deliver the message. This address should be a verified sender in your Twilio SendGrid account.",
"format": "email",
"type": "string"
},
"msg_id": {
"description": "A unique ID assigned to the message. This ID can be used to retrieve activity data for the specific message.",
"type": "string"
},
"status": {
"description": "The message's status.",
"enum": [
"processed",
"delivered",
"not_delivered"
],
"type": "string"
},
"subject": {
"description": "The email's subject line.",
"type": "string"
},
"to_email": {
"description": "The intended recipient's email address.",
"format": "email",
"type": "string"
}
},
"title": "Email Activity Response Common Fields",
"type": "object",
"x-stoplight": {
"id": "email-activity-response-common-fields",
"name": "Email Activity Response Common Fields",
"public": true
}
},
"enforced-tls-request-response": {
"example": {
"require_tls": true,
"require_valid_cert": true
},
"properties": {
"require_tls": {
"description": "Indicates if you want to require your recipients to support TLS. ",
"type": "boolean"
},
"require_valid_cert": {
"description": "Indicates if you want to require your recipients to have a valid certificate.",
"type": "boolean"
}
},
"title": "Enforced TLS Request Response",
"type": "object",
"x-stoplight": {
"id": "enforced-tls-request-response",
"name": "Enforced TLS Request Response",
"public": true
}
},
"error": {
"properties": {
"error_id": {
"type": "string"
},
"field": {
"type": "string"
},
"message": {
"type": "string"
},
"parameter": {
"type": "string"
}
},
"required": [
"message"
],
"title": "error",
"type": "object",
"x-stoplight": {
"id": "error",
"name": "error",
"public": true
}
},
"errors": {
"description": "If the request is incorrect, an array of errors will be returned.",
"properties": {
"errors": {
"items": {
"properties": {
"field": {
"nullable": true,
"type": "string"
},
"message": {
"description": "A description of what is wrong with the field passed in the request.",
"nullable": true,
"type": "string"
},
"parameter": {
"description": "The parameter in the request body that is incorrect.",
"type": "string"
}
},
"required": [
"parameter",
"message"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"errors"
],
"title": "Errors",
"type": "object",
"x-stoplight": {
"id": "errors",
"name": "errors",
"public": true
}
},
"errors-seg-v2": {
"description": "If the request is incorrect, an array of errors will be returned.",
"properties": {
"errors": {
"items": {
"properties": {
"field": {
"description": "the field in the request body that is incorrect",
"type": "string"
},
"message": {
"description": "a description of what is specifically wrong with the field passed in the request",
"type": "string"
}
},
"required": [
"field",
"message"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"errors"
],
"title": "errors-seg",
"type": "object",
"x-stoplight": {
"id": "errors-seg-v2",
"name": "errors-seg",
"public": true
}
},
"event-webhook-response": {
"properties": {
"bounce": {
"description": "Receiving server could not or would not accept message.",
"type": "boolean"
},
"click": {
"description": "Recipient clicked on a link within the message. You need to enable Click Tracking for getting this type of event.",
"type": "boolean"
},
"deferred": {
"description": "Recipient's email server temporarily rejected message.",
"type": "boolean"
},
"delivered": {
"description": "Message has been successfully delivered to the receiving server.",
"type": "boolean"
},
"dropped": {
"description": "You may see the following drop reasons: Invalid SMTPAPI header, Spam Content (if spam checker app enabled), Unsubscribed Address, Bounced Address, Spam Reporting Address, Invalid, Recipient List over Package Quota",
"type": "boolean"
},
"enabled": {
"description": "Indicates if the event webhook is enabled.",
"type": "boolean"
},
"group_resubscribe": {
"description": "Recipient resubscribes to specific group by updating preferences. You need to enable Subscription Tracking for getting this type of event.",
"type": "boolean"
},
"group_unsubscribe": {
"description": "Recipient unsubscribe from specific group, by either direct link or updating preferences. You need to enable Subscription Tracking for getting this type of event.",
"type": "boolean"
},
"oauth_client_id": {
"description": "The client ID Twilio SendGrid sends to your OAuth server or service provider to generate an OAuth access token.",
"type": "string"
},
"oauth_token_url": {
"description": "The URL where Twilio SendGrid sends the Client ID and Client Secret to generate an access token. This should be your OAuth server or service provider.",
"type": "string"
},
"open": {
"description": "Recipient has opened the HTML message. You need to enable Open Tracking for getting this type of event.",
"type": "boolean"
},
"processed": {
"description": "Message has been received and is ready to be delivered.",
"type": "boolean"
},
"spam_report": {
"description": "Recipient marked a message as spam.",
"type": "boolean"
},
"unsubscribe": {
"description": "Recipient clicked on message's subscription management link. You need to enable Subscription Tracking for getting this type of event.",
"type": "boolean"
},
"url": {
"description": "The URL that you want the event webhook to POST to.",
"type": "string"
}
},
"required": [
"enabled",
"url",
"group_resubscribe",
"delivered",
"group_unsubscribe",
"spam_report",
"bounce",
"deferred",
"unsubscribe",
"processed",
"open",
"click",
"dropped"
],
"title": "Webhooks: Event Webhook Response",
"type": "object",
"x-stoplight": {
"id": "event-webhook-response",
"name": "Webhooks: Event Webhook Response",
"public": true
}
},
"event-webhook-update-oauth-request": {
"properties": {
"bounce": {
"description": "Receiving server could not or would not accept message.",
"type": "boolean"
},
"click": {
"description": "Recipient clicked on a link within the message. You need to enable Click Tracking for getting this type of event.",
"type": "boolean"
},
"deferred": {
"description": "Recipient's email server temporarily rejected message.",
"type": "boolean"
},
"delivered": {
"description": "Message has been successfully delivered to the receiving server.",
"type": "boolean"
},
"dropped": {
"description": "You may see the following drop reasons: Invalid SMTPAPI header, Spam Content (if spam checker app enabled), Unsubscribed Address, Bounced Address, Spam Reporting Address, Invalid, Recipient List over Package Quota",
"type": "boolean"
},
"enabled": {
"description": "Indicates if the event webhook is enabled.",
"type": "boolean"
},
"group_resubscribe": {
"description": "Recipient resubscribes to specific group by updating preferences. You need to enable Subscription Tracking for getting this type of event.",
"type": "boolean"
},
"group_unsubscribe": {
"description": "Recipient unsubscribe from specific group, by either direct link or updating preferences. You need to enable Subscription Tracking for getting this type of event.",
"type": "boolean"
},
"oauth_client_id": {
"description": "The client ID Twilio SendGrid sends to your OAuth server or service provider to generate an OAuth access token. When passing data in this field, you must also include the oauth_token_url field.",
"type": "string"
},
"oauth_client_secret": {
"description": "This secret is needed only once to create an access token. SendGrid will store this secret, allowing you to update your Client ID and Token URL without passing the secret to SendGrid again. When passing data in this field, you must also include the oauth_client_id and oauth_token_url fields.",
"type": "string"
},
"oauth_token_url": {
"description": "The URL where Twilio SendGrid sends the Client ID and Client Secret to generate an access token. This should be your OAuth server or service provider. When passing data in this field, you must also include the oauth_client_id field.",
"type": "string"
},
"open": {
"description": "Recipient has opened the HTML message. You need to enable Open Tracking for getting this type of event.",
"type": "boolean"
},
"processed": {
"description": "Message has been received and is ready to be delivered.",
"type": "boolean"
},
"spam_report": {
"description": "Recipient marked a message as spam.",
"type": "boolean"
},
"unsubscribe": {
"description": "Recipient clicked on message's subscription management link. You need to enable Subscription Tracking for getting this type of event.",
"type": "boolean"
},
"url": {
"description": "The URL that you want the event webhook to POST to.",
"type": "string"
}
},
"required": [
"enabled",
"url",
"group_resubscribe",
"delivered",
"group_unsubscribe",
"spam_report",
"bounce",
"deferred",
"unsubscribe",
"processed",
"open",
"click",
"dropped"
],
"title": "Webhooks: Event Webhook Update with OAuth Request",
"type": "object",
"x-stoplight": {
"id": "event-webhook-update-oauth-request",
"name": "Webhooks: Event Webhook Update with OAuth Request",
"public": true
}
},
"from_email_object": {
"example": {
"email": "jane_doe@example.com",
"name": "Jane Doe"
},
"properties": {
"email": {
"description": "The 'From' email address used to deliver the message. This address should be a verified sender in your Twilio SendGrid account.",
"format": "email",
"type": "string"
},
"name": {
"description": "A name or title associated with the sending email address.",
"type": "string"
}
},
"required": [
"email"
],
"title": "From Email Object",
"type": "object",
"x-stoplight": {
"id": "from_email_object",
"name": "From Email Object",
"public": true
}
},
"full-segment": {
"allOf": [
{
"$ref": "#/components/schemas/segment_summary"
},
{
"properties": {
"contacts_sample": {
"items": {
"$ref": "#/components/schemas/contact_response"
},
"type": "array"
},
"query_json": {
"description": "AST representation of the query DSL",
"type": "object"
}
},
"required": [
"contacts_sample"
],
"type": "object"
},
{
"$ref": "#/components/schemas/segment_write_v2"
}
],
"example": {
"contacts_count": 9266921,
"contacts_sample": [
{
"address_line_1": "sunt aliqua",
"address_line_2": "sit proident Lorem veniam labore",
"alternate_emails": [
"yKDUP11FDch@QoU.vwy",
"ZNSN5@czAMqPi.at",
"7wr51kFVVKlcBSH@DWxOueOZepetzBrku.qosk",
"iib-ObtO7Fxz5@vLJPRIFKPOqJGCEqcIDab.ypn"
],
"city": "ȎţȸÛ\tč\u000bCŁ",
"contact_id": "c1183ada-b784-49ac-9b1f-50e73578a6dc",
"country": "do reprehenderit qui",
"custom_fields": {
"custom_field_name1": "esse",
"custom_field_name2": "in consectetur ut aliqua sint"
},
"first_name": "est",
"last_name": "eiusmod in laboris velit cupidatat",
"postal_code": 30296612,
"primary_email": "ft88@d.izxx",
"state_province_region": "ut proident"
}
],
"created_at": "2085-08-08T21:07:05.692Z",
"id": "58567a46-305e-48d1-b4f8-a006c906920e",
"name": "culpa",
"next_sample_update": "",
"parent_list_id": "2357714d-3d82-4c80-826c-b44a4147f81c",
"query_dsl": "cillum eiusmod",
"sample_updated_at": "3407-09-25T04:25:02.140Z",
"updated_at": "4431-05-07T22:23:22.352Z"
},
"title": "full_segment",
"x-stoplight": {
"id": "full-segment",
"name": "full_segment",
"public": true
}
},
"global_empty_request": {
"nullable": true,
"title": "Global: Request Empty Body",
"x-stoplight": {
"id": "global:empty_request",
"name": "Global: Request Empty Body",
"public": true
}
},
"global_error_response_schema": {
"example": {
"errors": [
{
"field": "field_name",
"message": "error message"
}
]
},
"properties": {
"errors": {
"items": {
"properties": {
"field": {
"description": "the field that generated the error",
"nullable": true,
"type": "string"
},
"help": {
"description": "helper text or docs for troubleshooting",
"type": "object"
},
"message": {
"description": "the error message",
"type": "string"
}
},
"required": [
"message"
],
"type": "object"
},
"type": "array"
},
"id": {
"type": "string"
}
},
"title": "Global Error Response Schema",
"type": "object",
"x-stoplight": {
"id": "global_error_response_schema",
"name": "Global Error Response Schema",
"public": true
}
},
"global_id": {
"title": "Global: ID",
"type": "integer",
"x-stoplight": {
"id": "global:id",
"name": "Global: ID",
"public": true
}
},
"google_analytics_settings": {
"example": {
"enabled": true,
"utm_campaign": "website",
"utm_content": "",
"utm_medium": "email",
"utm_source": "sendgrid.com",
"utm_term": ""
},
"properties": {
"enabled": {
"description": "Indicates if Google Analytics is enabled.",
"type": "boolean"
},
"utm_campaign": {
"description": "The name of the campaign.",
"type": "string"
},
"utm_content": {
"description": "Used to differentiate ads",
"type": "string"
},
"utm_medium": {
"description": "Name of the marketing medium (e.g. \"Email\").",
"type": "string"
},
"utm_source": {
"description": "Name of the referrer source. ",
"type": "string"
},
"utm_term": {
"description": "Any paid keywords.",
"type": "string"
}
},
"title": "Settings: Google Analytics",
"type": "object",
"x-stoplight": {
"id": "google_analytics_settings",
"name": "Settings: Google Analytics",
"public": true
}
},
"invalid-email": {
"example": {
"created": 1620141015,
"email": "invalid@example.com",
"reason": "Mail domain mentioned in email address is unknown"
},
"properties": {
"created": {
"description": "A Unix timestamp indicating when the email address was added to the invalid emails list.",
"type": "integer"
},
"email": {
"description": "The email address that was marked as invalid.",
"format": "email",
"type": "string"
},
"reason": {
"description": "The reason that the email address was marked as invalid.",
"type": "string"
}
},
"title": "Invalid Email",
"type": "object",
"x-stoplight": {
"id": "invalid-email",
"name": "Invalid Email",
"public": true
}
},
"ip-access-response": {
"example": {
"result": [
{
"created_at": 1441824715,
"id": 1,
"ip": "192.168.1.1/32",
"updated_at": 1441824715
},
{
"created_at": 1441824715,
"id": 2,
"ip": "192.0.0.0/8",
"updated_at": 1441824715
},
{
"created_at": 1441824715,
"id": 3,
"ip": "192.168.1.3/32",
"updated_at": 1441824715
}
]
},
"properties": {
"result": {
"description": "An array listing all of your allowed IPs.",
"items": {
"properties": {
"created_at": {
"description": "A Unix timestamp indicating when the IP was added to the allow list.",
"type": "integer"
},
"id": {
"description": "The ID of the allowed IP.",
"type": "integer"
},
"ip": {
"description": "The allowed IP.",
"type": "string"
},
"updated_at": {
"description": "A Unix timestamp indicating when the IPs allow status was most recently updated.",
"type": "integer"
}
},
"type": "object"
},
"type": "array"
}
},
"title": "IP Access Response",
"type": "object",
"x-stoplight": {
"id": "ip-access-response",
"name": "IP Access Response",
"public": true
}
},
"ip_pool": {
"properties": {
"name": {
"description": "The name of the IP pool.",
"maxLength": 64,
"type": "string"
}
},
"required": [
"name"
],
"title": "IP Pools: Pool",
"type": "object",
"x-stoplight": {
"id": "ip_pool",
"name": "IP Pools: Pool",
"public": true
}
},
"ip_pool_response": {
"example": {
"name": "sunt sint enim"
},
"properties": {
"name": {
"description": "The name of the IP pool.",
"type": "string"
}
},
"title": "IP Pools: Pool Resp",
"type": "object",
"x-stoplight": {
"id": "ip_pool_response",
"name": "IP Pools: Pool Resp",
"public": true
}
},
"ip_warmup_response": {
"example": [
{
"ip": "0.0.0.0",
"start_date": 1409616000
}
],
"items": {
"properties": {
"ip": {
"description": "The IP address.",
"type": "string"
},
"start_date": {
"description": "A Unix timestamp indicating when the IP address entered warmup mode.",
"type": "integer"
}
},
"required": [
"ip",
"start_date"
],
"type": "object"
},
"title": "IP Warmup: IP",
"type": "array",
"x-stoplight": {
"id": "ip_warmup_response",
"name": "IP Warmup: IP",
"public": true
}
},
"link": {
"properties": {
"href": {
"type": "string"
},
"rel": {
"type": "string"
}
},
"title": "Link",
"type": "object",
"x-stoplight": {
"id": "link",
"name": "Link",
"public": true
}
},
"link-tracking-metadata": {
"properties": {
"count": {
"description": "The number of items in the entire list, i.e., across all pages.",
"type": "number"
},
"next": {
"description": "The URL of the next page of results. If this field isn't present, you're at the end of the list.",
"format": "uri",
"type": "string"
},
"prev": {
"description": "The URL of the previous page of results. If this field isn't present, you're at the start of the list.",
"format": "uri",
"type": "string"
},
"self": {
"description": "The URL of the current page of results.",
"format": "uri",
"type": "string"
}
},
"title": "link tracking metadata",
"type": "object",
"x-stoplight": {
"id": "link-tracking-metadata",
"name": "link tracking metadata",
"public": true
}
},
"link_branding_200_response": {
"properties": {
"default": {
"description": "Indicates if this is the default link branding.",
"enum": [
true,
false
],
"type": "boolean"
},
"dns": {
"description": "The DNS records generated for this link branding.",
"properties": {
"domain_cname": {
"description": "The DNS record generated to point to your link branding subdomain.",
"properties": {
"data": {
"description": "The domain that the DNS record points to.",
"type": "string"
},
"host": {
"description": "The domain that this link branding will use for the links in your email.",
"type": "string"
},
"type": {
"description": "The type of DNS record that was generated.",
"enum": [
"cname",
"txt",
"mx"
],
"type": "string"
},
"valid": {
"description": "Indicates if the DNS record is valid.",
"enum": [
true,
false
],
"type": "boolean"
}
},
"required": [
"valid",
"type",
"host",
"data"
],
"type": "object"
},
"owner_cname": {
"description": "The DNS record generated to verify who created the link branding.",
"properties": {
"data": {
"description": "The domain that the DNS record points to.",
"type": "string"
},
"host": {
"description": "Used to verify the link branding. The subdomain of this domain is the ID of the user who created the link branding.",
"type": "string"
},
"type": {
"description": "The type of DNS record generated.",
"enum": [
"cname",
"txt",
"mx"
],
"type": "string"
},
"valid": {
"description": "Indicates if the DNS record is valid.",
"enum": [
true,
false
],
"type": "boolean"
}
},
"required": [
"valid",
"host",
"data"
],
"type": "object"
}
},
"required": [
"domain_cname"
],
"type": "object"
},
"domain": {
"description": "The root domain of the branded link.",
"type": "string"
},
"id": {
"description": "The ID of the branded link.",
"type": "integer"
},
"legacy": {
"description": "Indicates if this link branding was created using the legacy whitelabel tool. If it is a legacy whitelabel, it will still function, but you'll need to create new link branding if you need to update it.",
"enum": [
true,
false
],
"type": "boolean"
},
"subdomain": {
"description": "The subdomain used to generate the DNS records for this link branding. This subdomain must be different from the subdomain used for your authenticated domain.",
"type": "string"
},
"user_id": {
"description": "The ID of the user that this link branding is associated with.",
"type": "integer"
},
"username": {
"description": "The username of the account that this link branding is associated with.",
"type": "string"
},
"valid": {
"description": "Indicates if this link branding is valid.",
"enum": [
true,
false
],
"type": "boolean"
}
},
"required": [
"id",
"domain",
"username",
"user_id",
"default",
"valid",
"legacy",
"dns"
],
"title": "Link Branding 200 Response",
"type": "object",
"x-stoplight": {
"id": "link_branding_200_response",
"name": "Link Branding 200 Response",
"public": true
}
},
"list": {
"properties": {
"_metadata": {
"$ref": "#/components/schemas/selfmetadata"
},
"contact_count": {
"description": "The number of contacts currently stored on the list.",
"type": "integer"
},
"id": {
"description": "The generated ID for your list.",
"maxLength": 36,
"minLength": 36,
"type": "string"
},
"name": {
"description": "The name you gave your list.",
"type": "string"
}
},
"title": "list",
"type": "object",
"x-stoplight": {
"id": "list",
"name": "list",
"public": true
}
},
"mail_batch_id": {
"example": {
"batch_id": "HkJ5yLYULb7Rj8GKSx7u025ouWVlMgAi"
},
"properties": {
"batch_id": {
"pattern": "^[a-zA-Z0-9\\-\\_]",
"type": "string"
}
},
"required": [
"batch_id"
],
"title": "Mail Batch ID",
"type": "object",
"x-stoplight": {
"id": "mail_batch_id",
"name": "Mail Batch ID",
"public": true
}
},
"mail_settings_address_whitelabel": {
"example": {
"enabled": true,
"list": [
"email1@example.com",
"example.com"
]
},
"properties": {
"enabled": {
"description": "Indicates if you have an email address whitelist enabled. ",
"type": "boolean"
},
"list": {
"description": "All email addresses that are currently on the whitelist.",
"items": {
"type": "string"
},
"type": "array"
}
},
"title": "Mail Settings: Address Whitelabel",
"type": "object",
"x-stoplight": {
"id": "mail_settings_address_whitelabel",
"name": "Mail Settings: Address Whitelabel",
"public": true
}
},
"mail_settings_bounce_purge": {
"example": {
"enabled": false,
"hard_bounces": null,
"soft_bounces": 1234
},
"properties": {
"enabled": {
"description": "Indicates if the bounce purge mail setting is enabled.",
"type": "boolean"
},
"hard_bounces": {
"description": "The number of days after which SendGrid will purge all contacts from your hard bounces suppression lists.",
"nullable": true,
"type": "integer"
},
"soft_bounces": {
"description": "The number of days after which SendGrid will purge all contacts from your soft bounces suppression lists.",
"nullable": true,
"type": "integer"
}
},
"title": "Mail Settings: Bounce Purge",
"type": "object",
"x-stoplight": {
"id": "mail_settings_bounce_purge",
"name": "Mail Settings: Bounce Purge",
"public": true
}
},
"mail_settings_footer": {
"example": {
"enabled": true,
"html_content": "Example HTML content",
"plain_content": "Example plain content"
},
"properties": {
"enabled": {
"description": "Indicates if the Footer mail setting is currently enabled.",
"type": "boolean"
},
"html_content": {
"description": "The custom HTML content of your email footer.",
"type": "string"
},
"plain_content": {
"description": "The plain text content of your email footer.",
"type": "string"
}
},
"title": "Mail Settings: Footer",
"type": "object",
"x-stoplight": {
"id": "mail_settings_footer",
"name": "Mail Settings: Footer",
"public": true
}
},
"mail_settings_forward_bounce": {
"example": {
"email": null,
"enabled": false
},
"properties": {
"email": {
"description": "The email address that you would like your bounce reports forwarded to.",
"nullable": true,
"type": "string"
},
"enabled": {
"description": "Indicates if the bounce forwarding mail setting is enabled.",
"type": "boolean"
}
},
"title": "Mail Settings: Forward Bounce",
"type": "object",
"x-stoplight": {
"id": "mail_settings_forward_bounce",
"name": "Mail Settings: Forward Bounce",
"public": true
}
},
"mail_settings_forward_spam": {
"example": {
"email": "",
"enabled": true
},
"properties": {
"email": {
"description": "The email address where you would like the spam reports to be forwarded.",
"type": "string"
},
"enabled": {
"description": "Indicates if the Forward Spam setting is enabled.",
"type": "boolean"
}
},
"title": "Mail Settings: Forward Spam",
"type": "object",
"x-stoplight": {
"id": "mail_settings_forward_spam",
"name": "Mail Settings: Forward Spam",
"public": true
}
},
"mail_settings_patch": {
"example": {
"email": "email@example.com",
"enabled": true
},
"properties": {
"email": {
"description": "The email address of the recipient.",
"type": "string"
},
"enabled": {
"description": "Indicates if the mail setting is enabled.",
"type": "boolean"
}
},
"title": "Mail Settings: Patch",
"type": "object",
"x-stoplight": {
"id": "mail_settings_patch",
"name": "Mail Settings: Patch",
"public": true
}
},
"mail_settings_template": {
"example": {
"enabled": false,
"html_content": "<% body %>Example
\n"
},
"properties": {
"enabled": {
"description": "Indicates if the legacy email template setting is enabled.",
"type": "boolean"
},
"html_content": {
"description": "The HTML content that you want to use for your legacy email template.",
"type": "string"
}
},
"title": "Mail Settings: Template",
"type": "object",
"x-stoplight": {
"id": "mail_settings_template",
"name": "Mail Settings: Template",
"public": true
}
},
"mako_event": {
"example": {
"bounce_type": "blocked",
"event_name": "bounced",
"http_user_agent": "in tempor ex dolore est",
"mx_server": "quis proident",
"processed": "2017-10-13T18:56:21Z",
"reason": "some reason",
"url": "http://3LX,MU}N=B8'd,K}>bEma{l~!ad%peIF}y>qHfLPWQ$l9b\\!6.1H?$Z9H\"il-_gZD>/JPYsGqH4x4_3v090TCtnFalXGFiAdooDxgrDAYNXShUywSxwYr8gKeyc/4sal4VJ3IxEWsG74V5MYQ0mz27jhy7n5DHsUtApQ6zXHS13uO5vYBlJHpJRfuT6/F5nIpkHre2w3eTtN7M6pg9V5stjnnsavKkzQxyTv15CMSDLFwR_BTZwofhWpyBU7B9ypYL79vT97N3LDZyoaM/fNsOLPIqfGBer_Mx9_StergbQYANyOmOSjR6pZof01ky/ZcNDhpu3CkSl4MTtQ3NMCX780pOKQ5SYIPigyvz9IC9WtrCNcOkTxdOPdY0_4MJU4EuTTPmGvO/14KaJCDjIjgrbIqpzuUEL5mET0t2VeVlwvtnOnlHaBE8sic20ze2E0Xt3ETqXyzVJRjLDKh/LWkW8OVp_xkLBCCW7LQngRukKcOiWjMXeCEhYI9HoZ0RsMEWZC8KzRaHc4OI0uXPD4M9pav1LGrI/_0t_RnBnfnqGKsBJr0kdQi/Y6QN_aeawIqX5hDNIU3MF/wWKVWLS0ZFbDfK6KVv5oAid83EpwKoazAMA8MTfEXvHQLO7k7XYWX1Il3eGXL6/wCA96I1SOabzJkZHo2HsFpIC/VBk52Lnpp0xtDH/OCdlQ5e4PpxXQeklp70LPOndr7QKSYEQNUc48n36ixvTjhgpgO8wHsFFYqGcuBMHg9oaCARppQomiQDWYuVPVDynJHdsM1_gWl4/NSs8Y9PL7DrQXOu0UiFRRE0TUsvgqyUgJzlGjUnRziyYeROO75D0K_3aTtbGbCmhaxecos40a1w0PDCNkFp1W/iHwY7922drhsoM6ShwqqwGpAh5HLuU6Q5gqyckeai6YN7HCh9DdHPhhJcatgtMHZDKfQUBVt9ecUlDgiCFF_OnRX/GpzttcsL8E2FoXL9_eAWvSqjodROqx7MZCA/ORdnR/IssPCYP1kTHTIL5mZxv4UGEpyNjUzt4GdSJJTm0nztltWDYX8_Ezl2JvpLVnGVTJxobb4yQIJhe3n64khbOFyFLKHWEniIolm/AxpZQYmseWlVqrIz3YXU59XaSbTTrdCHNhvwF1ogXiiggN6TZ2B3QY_mBEtAp/SD0ONPVqEUkTNAFWTgnnlv6ZIMdMbTw5uZwtFRlB7qDvQouml9kujGmRu6k7zZMTOwWowRNtpboLUcL2NzkVgK6N1Zi2vq/Nt4NJvM5_l1dpIIbwJv_CIcZQZOqPtRWULa2iVxfmJJQaqgLQPwSHQH1zuRJMhraEsPjqVQRC0pZpSt/24VBDN8y31Ye/y_ekWxMdZCvr978C/WrdcTi29kxjJLyT9BII7BsgT5vLuI2l7ntqRAhAUWMs/h9JR0i8RbX5OfB46q41/TfmSdgi97bCR2HfgflyypXwKhRfKYU2MVpu2Dd90WQUlm7hZV8dSfGusuMj/nPMpRVWcbnvlAdsehJCPbLv6n4qdLSPeoMBo32acAGgu1BwBG8JsBgbH43yYi5X7UdGRWKqm_ZbqaDEKH3ncU/uA8EOJb41VfGho4LUeOi1IeYwVAhFEyO6YbteYZecEubrNFZrWWjZUqhzouzY95TeWU8E4StCXVPKlYPiFiwUSX20kG0lVtDbAy/7u4f4x0cYlFOvI1UN1qoOExmNxnxzQQFeM5exWfW2JrRXq5e0UdAJr4q2o9Y_0WaGfhL/nP6Ei06YajDKr11dK5H0LX/9CGTC37HFZeopyopzP_7fvGFkqIRoGTS48pLaIFz3gwpQNlWXUFCsd/PnRlsqJ3SBQSgp_AQe2cP6iBNy2bJI8lkxwY5YVDDdjxusuCcafdjfs2aUa/4tr_iMnNBnd27GxjQI28_JGJlfbOaajVJOxuPMT4ELpYCfPiFjdSbJyE0/gCwtj0rgDKSLWJnOPJ5TAJ935gCqeIsBhOhfcZX413GdilBZRRYEjCVKfOuWzHZ3GW/8yjyk5e_WMNv5F6xggl07w90DBwpx/Q/iWfncqMuSfoeFeqHQkDL9F5W19j1cGuAcyfIYMAXztHXpgTKh9vZcsLYC7LcgKr4FQj3JjEvtnDG2PjcMjGF/MnbCRCz22Ho410_vE9M1Hpq0wdk_i5DbZKNoSwlPgey9URkpuX146TcDdsx_VWDenCepY5HwMr9CPOY9hzUs/c5AWeUMXk/gvsI81Jkv5rHpEnNBUZXYzfqkwQfffhmrc/StLCtzRRlja8dpsEWdkzoKR9Kdxq1qAs5f0sdrGjVRLTT_s1Q2P59zhA/QmS4bubi64cYot3gSIgdNnkjA2GjCp1ETVa548_U9B6boTKDVmaKJlVIDvqL84RC3WI7Er/8opi2lZ48W83Ur47BRh38oOnI0agrCyZz8bp1w_gfVRlSO8PS0i/l_/qxq5xpLbhPkdxVoyZVsNAZchfnmkIHyIk5IK6EUDXdMR21y6OvKW50ZbooAtk9ymynBj4dAYMsd25RV7FE1I1vRTsiDw52/.E5WC0Ymo2zn.qelSbhzr-4laArYiWP.dwJB6qm_6rs0Rm5UXYaYtUNbh76_jJp_X1xQUCDSgbr2KOkDU0\"Q/-4dV\"Yk3QGg[(O86=Pf\"e17K4'r{)kicofHSXcMmP@>VF^`~4j4F*L/1]tD+Lw!WI!@]*OZm6C`M$u96}*ObEma{l~!ad%peIF}y>qHfLPWQ$l9b\\!6.1H?$Z9H\"il-_gZD>/JPYsGqH4x4_3v090TCtnFalXGFiAdooDxgrDAYNXShUywSxwYr8gKeyc/4sal4VJ3IxEWsG74V5MYQ0mz27jhy7n5DHsUtApQ6zXHS13uO5vYBlJHpJRfuT6/F5nIpkHre2w3eTtN7M6pg9V5stjnnsavKkzQxyTv15CMSDLFwR_BTZwofhWpyBU7B9ypYL79vT97N3LDZyoaM/fNsOLPIqfGBer_Mx9_StergbQYANyOmOSjR6pZof01ky/ZcNDhpu3CkSl4MTtQ3NMCX780pOKQ5SYIPigyvz9IC9WtrCNcOkTxdOPdY0_4MJU4EuTTPmGvO/14KaJCDjIjgrbIqpzuUEL5mET0t2VeVlwvtnOnlHaBE8sic20ze2E0Xt3ETqXyzVJRjLDKh/LWkW8OVp_xkLBCCW7LQngRukKcOiWjMXeCEhYI9HoZ0RsMEWZC8KzRaHc4OI0uXPD4M9pav1LGrI/_0t_RnBnfnqGKsBJr0kdQi/Y6QN_aeawIqX5hDNIU3MF/wWKVWLS0ZFbDfK6KVv5oAid83EpwKoazAMA8MTfEXvHQLO7k7XYWX1Il3eGXL6/wCA96I1SOabzJkZHo2HsFpIC/VBk52Lnpp0xtDH/OCdlQ5e4PpxXQeklp70LPOndr7QKSYEQNUc48n36ixvTjhgpgO8wHsFFYqGcuBMHg9oaCARppQomiQDWYuVPVDynJHdsM1_gWl4/NSs8Y9PL7DrQXOu0UiFRRE0TUsvgqyUgJzlGjUnRziyYeROO75D0K_3aTtbGbCmhaxecos40a1w0PDCNkFp1W/iHwY7922drhsoM6ShwqqwGpAh5HLuU6Q5gqyckeai6YN7HCh9DdHPhhJcatgtMHZDKfQUBVt9ecUlDgiCFF_OnRX/GpzttcsL8E2FoXL9_eAWvSqjodROqx7MZCA/ORdnR/IssPCYP1kTHTIL5mZxv4UGEpyNjUzt4GdSJJTm0nztltWDYX8_Ezl2JvpLVnGVTJxobb4yQIJhe3n64khbOFyFLKHWEniIolm/AxpZQYmseWlVqrIz3YXU59XaSbTTrdCHNhvwF1ogXiiggN6TZ2B3QY_mBEtAp/SD0ONPVqEUkTNAFWTgnnlv6ZIMdMbTw5uZwtFRlB7qDvQouml9kujGmRu6k7zZMTOwWowRNtpboLUcL2NzkVgK6N1Zi2vq/Nt4NJvM5_l1dpIIbwJv_CIcZQZOqPtRWULa2iVxfmJJQaqgLQPwSHQH1zuRJMhraEsPjqVQRC0pZpSt/24VBDN8y31Ye/y_ekWxMdZCvr978C/WrdcTi29kxjJLyT9BII7BsgT5vLuI2l7ntqRAhAUWMs/h9JR0i8RbX5OfB46q41/TfmSdgi97bCR2HfgflyypXwKhRfKYU2MVpu2Dd90WQUlm7hZV8dSfGusuMj/nPMpRVWcbnvlAdsehJCPbLv6n4qdLSPeoMBo32acAGgu1BwBG8JsBgbH43yYi5X7UdGRWKqm_ZbqaDEKH3ncU/uA8EOJb41VfGho4LUeOi1IeYwVAhFEyO6YbteYZecEubrNFZrWWjZUqhzouzY95TeWU8E4StCXVPKlYPiFiwUSX20kG0lVtDbAy/7u4f4x0cYlFOvI1UN1qoOExmNxnxzQQFeM5exWfW2JrRXq5e0UdAJr4q2o9Y_0WaGfhL/nP6Ei06YajDKr11dK5H0LX/9CGTC37HFZeopyopzP_7fvGFkqIRoGTS48pLaIFz3gwpQNlWXUFCsd/PnRlsqJ3SBQSgp_AQe2cP6iBNy2bJI8lkxwY5YVDDdjxusuCcafdjfs2aUa/4tr_iMnNBnd27GxjQI28_JGJlfbOaajVJOxuPMT4ELpYCfPiFjdSbJyE0/gCwtj0rgDKSLWJnOPJ5TAJ935gCqeIsBhOhfcZX413GdilBZRRYEjCVKfOuWzHZ3GW/8yjyk5e_WMNv5F6xggl07w90DBwpx/Q/iWfncqMuSfoeFeqHQkDL9F5W19j1cGuAcyfIYMAXztHXpgTKh9vZcsLYC7LcgKr4FQj3JjEvtnDG2PjcMjGF/MnbCRCz22Ho410_vE9M1Hpq0wdk_i5DbZKNoSwlPgey9URkpuX146TcDdsx_VWDenCepY5HwMr9CPOY9hzUs/c5AWeUMXk/gvsI81Jkv5rHpEnNBUZXYzfqkwQfffhmrc/StLCtzRRlja8dpsEWdkzoKR9Kdxq1qAs5f0sdrGjVRLTT_s1Q2P59zhA/QmS4bubi64cYot3gSIgdNnkjA2GjCp1ETVa548_U9B6boTKDVmaKJlVIDvqL84RC3WI7Er/8opi2lZ48W83Ur47BRh38oOnI0agrCyZz8bp1w_gfVRlSO8PS0i/l_/qxq5xpLbhPkdxVoyZVsNAZchfnmkIHyIk5IK6EUDXdMR21y6OvKW50ZbooAtk9ymynBj4dAYMsd25RV7FE1I1vRTsiDw52/.E5WC0Ymo2zn.qelSbhzr-4laArYiWP.dwJB6qm_6rs0Rm5UXYaYtUNbh76_jJp_X1xQUCDSgbr2KOkDU0\"Q/-4dV\"Yk3QGg[(O86=Pf\"e17K4'r{)kicofHSXcMmP@>VF^`~4j4F*L/1]tD+Lw!WI!@]*OZm6C`M$u96}*ObEma{l~!ad%peIF}y>qHfLPWQ$l9b\\!6.1H?$Z9H\"il-_gZD>/JPYsGqH4x4_3v090TCtnFalXGFiAdooDxgrDAYNXShUywSxwYr8gKeyc/4sal4VJ3IxEWsG74V5MYQ0mz27jhy7n5DHsUtApQ6zXHS13uO5vYBlJHpJRfuT6/F5nIpkHre2w3eTtN7M6pg9V5stjnnsavKkzQxyTv15CMSDLFwR_BTZwofhWpyBU7B9ypYL79vT97N3LDZyoaM/fNsOLPIqfGBer_Mx9_StergbQYANyOmOSjR6pZof01ky/ZcNDhpu3CkSl4MTtQ3NMCX780pOKQ5SYIPigyvz9IC9WtrCNcOkTxdOPdY0_4MJU4EuTTPmGvO/14KaJCDjIjgrbIqpzuUEL5mET0t2VeVlwvtnOnlHaBE8sic20ze2E0Xt3ETqXyzVJRjLDKh/LWkW8OVp_xkLBCCW7LQngRukKcOiWjMXeCEhYI9HoZ0RsMEWZC8KzRaHc4OI0uXPD4M9pav1LGrI/_0t_RnBnfnqGKsBJr0kdQi/Y6QN_aeawIqX5hDNIU3MF/wWKVWLS0ZFbDfK6KVv5oAid83EpwKoazAMA8MTfEXvHQLO7k7XYWX1Il3eGXL6/wCA96I1SOabzJkZHo2HsFpIC/VBk52Lnpp0xtDH/OCdlQ5e4PpxXQeklp70LPOndr7QKSYEQNUc48n36ixvTjhgpgO8wHsFFYqGcuBMHg9oaCARppQomiQDWYuVPVDynJHdsM1_gWl4/NSs8Y9PL7DrQXOu0UiFRRE0TUsvgqyUgJzlGjUnRziyYeROO75D0K_3aTtbGbCmhaxecos40a1w0PDCNkFp1W/iHwY7922drhsoM6ShwqqwGpAh5HLuU6Q5gqyckeai6YN7HCh9DdHPhhJcatgtMHZDKfQUBVt9ecUlDgiCFF_OnRX/GpzttcsL8E2FoXL9_eAWvSqjodROqx7MZCA/ORdnR/IssPCYP1kTHTIL5mZxv4UGEpyNjUzt4GdSJJTm0nztltWDYX8_Ezl2JvpLVnGVTJxobb4yQIJhe3n64khbOFyFLKHWEniIolm/AxpZQYmseWlVqrIz3YXU59XaSbTTrdCHNhvwF1ogXiiggN6TZ2B3QY_mBEtAp/SD0ONPVqEUkTNAFWTgnnlv6ZIMdMbTw5uZwtFRlB7qDvQouml9kujGmRu6k7zZMTOwWowRNtpboLUcL2NzkVgK6N1Zi2vq/Nt4NJvM5_l1dpIIbwJv_CIcZQZOqPtRWULa2iVxfmJJQaqgLQPwSHQH1zuRJMhraEsPjqVQRC0pZpSt/24VBDN8y31Ye/y_ekWxMdZCvr978C/WrdcTi29kxjJLyT9BII7BsgT5vLuI2l7ntqRAhAUWMs/h9JR0i8RbX5OfB46q41/TfmSdgi97bCR2HfgflyypXwKhRfKYU2MVpu2Dd90WQUlm7hZV8dSfGusuMj/nPMpRVWcbnvlAdsehJCPbLv6n4qdLSPeoMBo32acAGgu1BwBG8JsBgbH43yYi5X7UdGRWKqm_ZbqaDEKH3ncU/uA8EOJb41VfGho4LUeOi1IeYwVAhFEyO6YbteYZecEubrNFZrWWjZUqhzouzY95TeWU8E4StCXVPKlYPiFiwUSX20kG0lVtDbAy/7u4f4x0cYlFOvI1UN1qoOExmNxnxzQQFeM5exWfW2JrRXq5e0UdAJr4q2o9Y_0WaGfhL/nP6Ei06YajDKr11dK5H0LX/9CGTC37HFZeopyopzP_7fvGFkqIRoGTS48pLaIFz3gwpQNlWXUFCsd/PnRlsqJ3SBQSgp_AQe2cP6iBNy2bJI8lkxwY5YVDDdjxusuCcafdjfs2aUa/4tr_iMnNBnd27GxjQI28_JGJlfbOaajVJOxuPMT4ELpYCfPiFjdSbJyE0/gCwtj0rgDKSLWJnOPJ5TAJ935gCqeIsBhOhfcZX413GdilBZRRYEjCVKfOuWzHZ3GW/8yjyk5e_WMNv5F6xggl07w90DBwpx/Q/iWfncqMuSfoeFeqHQkDL9F5W19j1cGuAcyfIYMAXztHXpgTKh9vZcsLYC7LcgKr4FQj3JjEvtnDG2PjcMjGF/MnbCRCz22Ho410_vE9M1Hpq0wdk_i5DbZKNoSwlPgey9URkpuX146TcDdsx_VWDenCepY5HwMr9CPOY9hzUs/c5AWeUMXk/gvsI81Jkv5rHpEnNBUZXYzfqkwQfffhmrc/StLCtzRRlja8dpsEWdkzoKR9Kdxq1qAs5f0sdrGjVRLTT_s1Q2P59zhA/QmS4bubi64cYot3gSIgdNnkjA2GjCp1ETVa548_U9B6boTKDVmaKJlVIDvqL84RC3WI7Er/8opi2lZ48W83Ur47BRh38oOnI0agrCyZz8bp1w_gfVRlSO8PS0i/l_/qxq5xpLbhPkdxVoyZVsNAZchfnmkIHyIk5IK6EUDXdMR21y6OvKW50ZbooAtk9ymynBj4dAYMsd25RV7FE1I1vRTsiDw52/.E5WC0Ymo2zn.qelSbhzr-4laArYiWP.dwJB6qm_6rs0Rm5UXYaYtUNbh76_jJp_X1xQUCDSgbr2KOkDU0\"Q/-4dV\"Yk3QGg[(O86=Pf\"e17K4'r{)kicofHSXcMmP@>VF^`~4j4F*L/1]tD+Lw!WI!@]*OZm6C`M$u96}*O"
},
"properties": {
"id": {
"description": "A unique ID assigned to the certificate by SendGrid.",
"type": "number"
},
"intergration_id": {
"description": "An ID that matches a certificate to a specific IdP integration.",
"type": "string"
},
"not_after": {
"description": "A unix timestamp (e.g., 1603915954) that indicates the time after which the certificate is no longer valid.",
"type": "number"
},
"not_before": {
"description": "A unix timestamp (e.g., 1603915954) that indicates the time before which the certificate is not valid.",
"type": "number"
},
"public_certificate": {
"description": "This certificate is used by Twilio SendGrid to verify that SAML requests are coming from Okta. This is called the X509 certificate in the Twilio SendGrid UI.",
"type": "string"
}
},
"title": "Single Sign-On Certificate Body",
"type": "object",
"x-stoplight": {
"id": "sso-certificate-body",
"name": "Single Sign-On Certificate Body",
"public": true
}
},
"sso-error-response": {
"items": {
"properties": {
"error_id": {
"type": "string"
},
"field": {
"nullable": true,
"type": "string"
},
"message": {
"type": "string"
}
},
"type": "object"
},
"title": "SSO Error Response",
"type": "array",
"x-stoplight": {
"id": "sso-error-response",
"name": "SSO Error Response",
"public": true
}
},
"sso-integration": {
"allOf": [
{
"$ref": "#/components/schemas/create-integration-request"
},
{
"properties": {
"audience_url": {
"description": "The URL where your IdP should POST its SAML response. This is the Twilio SendGrid URL that is responsible for receiving and parsing a SAML assertion. This is the same URL as the Single Sign-On URL when using SendGrid.",
"type": "string"
},
"id": {
"description": "A unique ID assigned to the configuration by SendGrid.",
"type": "string"
},
"last_updated": {
"description": "A timestamp representing the last time the configuration was modified.",
"type": "number"
},
"single_signon_url": {
"description": "The URL where your IdP should POST its SAML response. This is the Twilio SendGrid URL that is responsible for receiving and parsing a SAML assertion. This is the same URL as the Audience URL when using SendGrid.",
"type": "string"
}
},
"required": [
"last_updated"
],
"type": "object"
}
],
"title": "Single Sign-On Integration",
"x-stoplight": {
"id": "sso-integration",
"name": "Single Sign-On Integration",
"public": true
}
},
"sso-teammate-common-fields": {
"properties": {
"email": {
"description": "The Teammate’s email address. This email address will also function as the Teammate’s username and must match the address assigned to the user in your IdP. This address cannot be changed after the Teammate is created.",
"format": "email",
"type": "string"
},
"first_name": {
"description": "The Teammate’s first name.",
"type": "string"
},
"is_admin": {
"description": "Indicates if the Teammate has admin permissions.",
"type": "boolean"
},
"is_read_only": {
"description": "Indicates if the Teammate has read_only permissions.",
"type": "boolean"
},
"last_name": {
"description": "The Teammate’s last name.",
"type": "string"
}
},
"required": [
"first_name",
"last_name",
"email"
],
"title": "Single Sing-On Teammate Common Fields",
"type": "object",
"x-stoplight": {
"id": "sso-teammate-common-fields",
"name": "Single Sing-On Teammate Common Fields",
"public": true
}
},
"sso-teammate-request": {
"allOf": [
{
"$ref": "#/components/schemas/sso-teammate-common-fields"
},
{
"properties": {
"scopes": {
"description": "The permission scopes assigned to the Teammate.",
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"scopes"
],
"type": "object"
}
],
"title": "Single Sign-On Teammate Request",
"x-stoplight": {
"id": "sso-teammate-request",
"name": "Single Sign-On Teammate Request",
"public": true
}
},
"sso-teammate-response": {
"allOf": [
{
"$ref": "#/components/schemas/sso-teammate-common-fields"
},
{
"properties": {
"is_sso": {
"description": "Indicates if the Teammate authenticates with SendGrid using SSO or with a username and password.",
"type": "boolean"
},
"username": {
"description": "This should be set to the Teammate's email address.",
"type": "string"
}
},
"type": "object"
}
],
"title": "Single Sign-On Teammate Response",
"x-stoplight": {
"id": "sso-teammate-response",
"name": "Single Sign-On Teammate Response",
"public": true
}
},
"sso-teammates-patch-response": {
"allOf": [
{
"$ref": "#/components/schemas/sso-teammate-response"
},
{
"properties": {
"address": {
"description": "The Teammate’s street address.",
"type": "string"
},
"address2": {
"description": "The Teammate’s apartment number, suite number, or other secondary address information that is not part of the physical street address.",
"type": "string"
},
"city": {
"description": "The Teammate's city.",
"type": "string"
},
"company": {
"description": "The Teammate’s company name.",
"type": "string"
},
"country": {
"description": "The Teammate’s country of residence.",
"type": "string"
},
"email": {
"format": "email",
"type": "string"
},
"phone": {
"description": "The Teammate’s stored phone number.",
"type": "string"
},
"scopes": {
"description": "The permission scopes assigned to the Teammate.",
"items": {
"type": "string"
},
"type": "array"
},
"state": {
"description": "The Teammate’s state or province.",
"type": "string"
},
"user_type": {
"description": "A Teammate can be an “admin,” “owner,” or “teammate.” Each role is associated with the scope of the Teammate’s permissions.",
"enum": [
"admin",
"owner",
"teammate"
],
"type": "string"
},
"website": {
"description": "A website associated with the Teammate",
"type": "string"
},
"zip": {
"description": "The Teammate’s zip code.",
"type": "string"
}
},
"type": "object"
}
],
"title": "Single Sign-On Teammates PATCH Response",
"x-stoplight": {
"id": "sso-teammates-patch-response",
"name": "Single Sign-On Teammates PATCH Response",
"public": true
}
},
"stats-advanced-global-stats": {
"allOf": [
{
"$ref": "#/components/schemas/advanced_stats_clicks_opens"
},
{
"properties": {
"blocks": {
"description": "The number of emails that were not allowed to be delivered by ISPs.",
"type": "integer"
},
"bounce_drops": {
"description": "The number of emails that were dropped because of a bounce.",
"type": "integer"
},
"bounces": {
"description": "The number of emails that bounced instead of being delivered.",
"type": "integer"
},
"deferred": {
"description": "The number of emails that temporarily could not be delivered. ",
"type": "integer"
},
"delivered": {
"description": "The number of emails SendGrid was able to confirm were actually delivered to a recipient.",
"type": "integer"
},
"invalid_emails": {
"description": "The number of recipients who had malformed email addresses or whose mail provider reported the address as invalid.",
"type": "integer"
},
"processed": {
"description": "Requests from your website, application, or mail client via SMTP Relay or the API that SendGrid processed.",
"type": "integer"
},
"requests": {
"description": "The number of emails that were requested to be delivered.",
"type": "integer"
},
"spam_report_drops": {
"description": "The number of emails that were dropped due to a recipient previously marking your emails as spam.",
"type": "integer"
},
"spam_reports": {
"description": "The number of recipients who marked your email as spam.",
"type": "integer"
},
"unsubscribe_drops": {
"description": "The number of emails dropped due to a recipient unsubscribing from your emails.",
"type": "integer"
},
"unsubscribes": {
"description": "The number of recipients who unsubscribed from your emails.",
"type": "integer"
}
},
"type": "object"
}
],
"title": "Stats: Advanced Global Stats",
"x-stoplight": {
"id": "stats-advanced-global-stats",
"name": "Stats: Advanced Global Stats",
"public": true
}
},
"stats-advanced-stats-base-schema": {
"items": {
"properties": {
"date": {
"description": "The date the stats were gathered.",
"type": "string"
},
"stats": {
"description": "The individual email activity stats.",
"items": {
"properties": {
"metrics": {
"type": "object"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
},
"title": "Stats: Advanced Stats Base Schema",
"type": "array",
"x-stoplight": {
"id": "stats-advanced-stats-base-schema",
"name": "Stats: Advanced Stats Base Schema",
"public": true
}
},
"subscription_tracking_settings": {
"properties": {
"enabled": {
"description": "Indicates if subscription tracking is enabled.",
"type": "boolean"
},
"html_content": {
"description": "The information and HTML for your unsubscribe link. ",
"type": "string"
},
"landing": {
"description": "The HTML that will be displayed on the page that your customers will see after clicking unsubscribe, hosted on SendGrid’s server.",
"type": "string"
},
"plain_content": {
"description": "The information in plain text for your unsubscribe link. You should have the “<% %>” tag in your content, otherwise the user will have no URL for unsubscribing.",
"type": "string"
},
"replace": {
"description": "Your custom defined replacement tag for your templates. Use this tag to place your unsubscribe content anywhere in your emailtemplate.",
"type": "string"
},
"url": {
"description": "The URL where you would like your users sent to unsubscribe.",
"format": "uri",
"type": "string"
}
},
"title": "Settings: Subscription Tracking",
"type": "object",
"x-stoplight": {
"id": "subscription_tracking_settings",
"name": "Settings: Subscription Tracking",
"public": true
}
},
"subuser": {
"example": {
"disabled": false,
"email": "example@example.com",
"id": 1234,
"username": "example_subuser"
},
"properties": {
"disabled": {
"description": "Whether or not the user is enabled or disabled.",
"type": "boolean"
},
"email": {
"description": "The email address to contact this subuser.",
"format": "email",
"type": "string"
},
"id": {
"description": "The ID of this subuser.",
"type": "number"
},
"username": {
"description": "The name by which this subuser will be referred.",
"type": "string"
}
},
"required": [
"disabled",
"id",
"username",
"email"
],
"title": "List all Subusers for a parent response",
"type": "object",
"x-stoplight": {
"id": "subuser",
"name": "Subuser",
"public": true
}
},
"subuser_post": {
"example": {
"authorization_token": "",
"credit_allocation": {
"type": "unlimited"
},
"email": "example@example.com",
"signup_session_token": "",
"user_id": 1234,
"username": "example_subuser"
},
"properties": {
"authorization_token": {
"type": "string"
},
"credit_allocation": {
"properties": {
"type": {
"type": "string"
}
},
"type": "object"
},
"email": {
"description": "The email address for this subuser.",
"format": "email",
"type": "string"
},
"signup_session_token": {
"type": "string"
},
"user_id": {
"description": "The user ID for this subuser.",
"type": "number"
},
"username": {
"description": "The username of the subuser.",
"type": "string"
}
},
"required": [
"username",
"user_id",
"email"
],
"title": "Subuser::POST",
"type": "object",
"x-stoplight": {
"id": "subuser_post",
"name": "Subuser::POST",
"public": true
}
},
"subuser_stats": {
"example": {
"date": "2016-02-01",
"stats": [
{
"first_name": "John",
"last_name": "Doe",
"metrics": {
"blocks": 0,
"bounce_drops": 0,
"bounces": 0,
"clicks": 5,
"deferred": 0,
"delivered": 0,
"invalid_emails": 0,
"opens": 10,
"processed": 10,
"requests": 10,
"spam_report_drops": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0,
"unsubscribe_drops": 0,
"unsubscribes": 0
},
"name": "user1",
"type": "subuser"
}
]
},
"properties": {
"date": {
"description": "The date the statistics were gathered.",
"type": "string"
},
"stats": {
"description": "The list of statistics.",
"items": {
"properties": {
"first_name": {
"description": "The first name of the subuser.",
"type": "string"
},
"last_name": {
"description": "The last name of the subuser.",
"type": "string"
},
"metrics": {
"properties": {
"blocks": {
"description": "The number of emails that were not allowed to be delivered by ISPs.",
"type": "integer"
},
"bounce_drops": {
"description": "The number of emails that were dropped because of a bounce.",
"type": "integer"
},
"bounces": {
"description": "The number of emails that bounced instead of being delivered.",
"type": "integer"
},
"clicks": {
"description": "The number of links that were clicked in your emails.",
"type": "integer"
},
"deferred": {
"description": "The number of emails that temporarily could not be delivered.",
"type": "integer"
},
"delivered": {
"description": "The number of emails SendGrid was able to confirm were actually delivered to a recipient.",
"type": "integer"
},
"invalid_emails": {
"description": "The number of recipients who had malformed email addresses or whose mail provider reported the address as invalid.",
"type": "integer"
},
"opens": {
"description": "The total number of times your emails were opened by recipients.",
"type": "integer"
},
"processed": {
"description": "Requests from your website, application, or mail client via SMTP Relay or the API that SendGrid processed.",
"type": "integer"
},
"requests": {
"description": "The number of emails that were requested to be delivered.",
"type": "integer"
},
"spam_report_drops": {
"description": "The number of emails that were dropped due to a recipient previously marking your emails as spam.",
"type": "integer"
},
"spam_reports": {
"description": "The number of recipients who marked your email as spam.",
"type": "integer"
},
"unique_clicks": {
"description": "The number of unique recipients who clicked links in your emails.",
"type": "integer"
},
"unique_opens": {
"description": "The number of unique recipients who opened your emails.",
"type": "integer"
},
"unsubscribe_drops": {
"description": "The number of emails dropped due to a recipient unsubscribing from your emails.",
"type": "integer"
},
"unsubscribes": {
"description": "The number of recipients who unsubscribed from your emails.",
"type": "integer"
}
},
"type": "object"
},
"name": {
"description": "The username of the subuser.",
"type": "string"
},
"type": {
"description": "The type of account.",
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"title": "subuser_stats",
"type": "object",
"x-stoplight": {
"id": "subuser_stats",
"name": "subuser_stats",
"public": true
}
},
"suppression-group-request-base": {
"properties": {
"description": {
"description": "A brief description of your suppression group. Required when creating a group.",
"maxLength": 100,
"type": "string"
},
"is_default": {
"description": "Indicates if you would like this to be your default suppression group.",
"type": "boolean"
},
"name": {
"description": "The name of your suppression group. Required when creating a group.",
"maxLength": 30,
"type": "string"
}
},
"title": "Suppression Group Request Base",
"type": "object",
"x-stoplight": {
"id": "suppression-group-request-base",
"name": "Suppression Group Request Base",
"public": true
}
},
"suppression_group": {
"properties": {
"description": {
"description": "A description of the suppression group.",
"maxLength": 100,
"type": "string"
},
"id": {
"description": "The id of the suppression group.",
"type": "number"
},
"is_default": {
"default": false,
"description": "Indicates if this is the default suppression group.",
"type": "boolean"
},
"last_email_sent_at": {
"nullable": true,
"type": "integer"
},
"name": {
"description": "The name of the suppression group. Each group created by a user must have a unique name.",
"maxLength": 30,
"type": "string"
},
"unsubscribes": {
"description": "The unsubscribes associated with this group.",
"type": "integer"
}
},
"required": [
"id",
"name",
"description"
],
"title": "Suppressions: Suppression Group",
"type": "object",
"x-stoplight": {
"id": "suppression_group",
"name": "Suppressions: Suppression Group",
"public": true
}
},
"suppressions-request": {
"example": {
"recipient_emails": [
"test1@example.com",
"test2@example.com"
]
},
"properties": {
"recipient_emails": {
"description": "The array of email addresses to add or find.",
"items": {
"format": "email",
"type": "string"
},
"type": "array"
}
},
"required": [
"recipient_emails"
],
"title": "Suppressions Request Body",
"type": "object",
"x-stoplight": {
"id": "suppressions-request",
"name": "Suppressions Request Body",
"public": true
}
},
"to_email_array": {
"example": [
{
"email": "john_doe@example.com",
"name": "John Doe"
}
],
"items": {
"properties": {
"email": {
"description": "The intended recipient's email address.",
"format": "email",
"type": "string"
},
"name": {
"description": "The intended recipient's name.",
"type": "string"
}
},
"required": [
"email"
],
"type": "object"
},
"title": "To Email Array",
"type": "array",
"x-stoplight": {
"id": "to_email_array",
"name": "To Email Array",
"public": true
}
},
"transactional-template-warning": {
"example": {
"message": "A sample warning message."
},
"properties": {
"message": {
"description": "Warning message for the user",
"type": "string"
}
},
"title": "Warning",
"type": "object",
"x-stoplight": {
"id": "transactional-template-warning",
"name": "Warning",
"public": true
}
},
"transactional-templates-template-lean": {
"example": {
"generation": "legacy",
"id": "0c314114-a2b7-4523-8cbc-a293d7d19007",
"name": "example_name",
"updated_at ": "2021-04-28 13:12:46",
"versions": []
},
"properties": {
"generation": {
"description": "Defines the generation of the template.",
"enum": [
"legacy",
"dynamic"
],
"type": "string"
},
"id": {
"description": "The ID of the transactional template.",
"format": "uuid",
"maxLength": 36,
"minLength": 36,
"type": "string"
},
"name": {
"description": "The name for the transactional template.",
"maxLength": 100,
"type": "string"
},
"updated_at ": {
"description": "The date and time that this transactional template version was updated.",
"pattern": "^(\\d{4}-\\d{2}-\\d{2}) ((\\d{2}):(\\d{2}):(\\d{2}))$",
"type": "string"
},
"versions": {
"description": "The different versions of this transactional template.",
"items": {
"$ref": "#/components/schemas/transactional-templates-version-output-lean"
},
"type": "array"
}
},
"required": [
"id",
"name",
"generation",
"updated_at "
],
"title": "Transactional Templates: Template Lean",
"type": "object",
"x-stoplight": {
"id": "transactional-templates-template-lean",
"name": "Transactional Templates: Template Lean",
"public": true
}
},
"transactional-templates-version-output-lean": {
"properties": {
"active": {
"description": "Set the version as the active version associated with the template. Only one version of a template can be active. The first version created for a template will automatically be set to Active.",
"enum": [
0,
1
],
"type": "integer"
},
"editor": {
"description": "The editor used in the UI.",
"enum": [
"code",
"design"
],
"type": "string"
},
"generate_plain_content": {
"default": true,
"description": "If true, plain_content is always generated from html_content. If false, plain_content is not altered.",
"type": "boolean"
},
"id": {
"description": "ID of the transactional template version.",
"format": "uuid",
"type": "string"
},
"name": {
"description": "Name of the transactional template version.",
"maxLength": 100,
"type": "string"
},
"subject": {
"description": "Subject of the new transactional template version.",
"maxLength": 255,
"type": "string"
},
"template_id": {
"description": "ID of the transactional template.",
"type": "string"
},
"thumbnail_url": {
"description": "A Thumbnail preview of the template's html content.",
"type": "string"
},
"updated_at": {
"description": "The date and time that this transactional template version was updated.",
"type": "string"
}
},
"title": "Transactional Templates: Version Output Lean",
"type": "object",
"x-stoplight": {
"id": "transactional-templates-version-output-lean",
"name": "Transactional Templates: Version Output Lean",
"public": true
}
},
"transactional_template": {
"allOf": [
{
"$ref": "#/components/schemas/transactional-templates-template-lean"
},
{
"properties": {
"warning": {
"$ref": "#/components/schemas/transactional-template-warning"
}
},
"type": "object"
}
],
"example": {
"generation": "legacy",
"id": "33feeff2-5069-43fe-8853-428651e5be79",
"name": "example_name",
"updated_at ": "2021-04-28 13:12:46",
"warning": {
"message": "Sample warning message"
}
},
"title": "Transactional Templates: Template",
"x-stoplight": {
"id": "transactional_template",
"name": "Transactional Templates: Template",
"public": true
}
},
"transactional_template_version_create": {
"example": {
"active": 1,
"editor": "design",
"generate_plain_content": false,
"html_content": "dolor",
"name": "pariatur non incididunt commodo",
"plain_content": "labore dolore",
"subject": "aliquip nulla Ut",
"template_id": "Excepteur Ut qui"
},
"properties": {
"active": {
"description": "Set the version as the active version associated with the template (0 is inactive, 1 is active). Only one version of a template can be active. The first version created for a template will automatically be set to Active.",
"enum": [
0,
1
],
"type": "integer"
},
"editor": {
"description": "The editor used in the UI.",
"enum": [
"code",
"design"
],
"type": "string"
},
"generate_plain_content": {
"default": true,
"description": "If true, plain_content is always generated from html_content. If false, plain_content is not altered.",
"type": "boolean"
},
"html_content": {
"description": "The HTML content of the version. Maximum of 1048576 bytes allowed.",
"maxLength": 1048576,
"type": "string"
},
"name": {
"description": "Name of the transactional template version.",
"maxLength": 100,
"type": "string"
},
"plain_content": {
"default": "",
"description": "Text/plain content of the transactional template version. Maximum of 1048576 bytes allowed.",
"maxLength": 1048576,
"type": "string"
},
"subject": {
"description": "Subject of the new transactional template version.",
"maxLength": 255,
"type": "string"
},
"test_data": {
"description": "For dynamic templates only, the mock json data that will be used for template preview and test sends.",
"type": "string"
}
},
"required": [
"name",
"subject"
],
"title": "Transactional Templates: Version Create",
"type": "object",
"x-stoplight": {
"id": "transactional_template_version_create",
"name": "Transactional Templates: Version Create",
"public": true
}
},
"transactional_template_version_output": {
"allOf": [
{
"properties": {
"warnings": {
"items": {
"$ref": "#/components/schemas/transactional-template-warning"
},
"type": "array"
}
},
"type": "object"
},
{
"$ref": "#/components/schemas/transactional_template_version_create"
},
{
"$ref": "#/components/schemas/transactional-templates-version-output-lean"
}
],
"title": "Transactional Templates: Version Output",
"x-stoplight": {
"id": "transactional_template_version_output",
"name": "Transactional Templates: Version Output",
"public": true
}
},
"user_profile": {
"example": {
"address": "1451 Larimer Street, 3rd floor",
"address2": "",
"city": "Denver, CO",
"company": "SendGrid",
"country": "US",
"first_name": "Matthew",
"last_name": "Bernier",
"phone": "7208788003",
"state": "CO",
"website": "http://sendgrid.com",
"zip": "80202"
},
"properties": {
"address": {
"description": "The street address for this user profile.",
"type": "string"
},
"address2": {
"description": "An optional second line for the street address of this user profile.",
"type": "string"
},
"city": {
"description": "The city for the user profile.",
"type": "string"
},
"company": {
"description": "That company that this user profile is associated with.",
"type": "string"
},
"country": {
"description": "Th country of this user profile.",
"type": "string"
},
"first_name": {
"description": "The first name of the user.",
"type": "string"
},
"last_name": {
"description": "The last name of the user.",
"type": "string"
},
"phone": {
"description": "The phone number for the user.",
"type": "string"
},
"state": {
"description": "The state for this user.",
"type": "string"
},
"website": {
"description": "The website associated with this user.",
"type": "string"
},
"zip": {
"description": "The zip code for this user.",
"type": "string"
}
},
"title": "User: Profile",
"type": "object",
"x-stoplight": {
"id": "user_profile",
"name": "User: Profile",
"public": true
}
},
"user_scheduled_send_status": {
"allOf": [
{
"$ref": "#/components/schemas/mail_batch_id"
},
{
"description": "The status of the scheduled send.",
"properties": {
"status": {
"description": "The status of the scheduled send.",
"enum": [
"cancel",
"pause"
],
"type": "string"
}
},
"required": [
"status"
],
"type": "object"
}
],
"example": {
"batch_id": "HkJ5yLYULb7Rj8GKSx7u025ouWVlMgAi",
"status": "pause"
},
"title": "User Scheduled Send status",
"x-stoplight": {
"id": "user_scheduled_send_status",
"name": "User Scheduled Send status",
"public": true
}
},
"verified-sender-request-schema": {
"example": {
"address": "1234 Fake St",
"address2": "PO Box 1234",
"city": "San Francisco",
"country": "USA",
"from_email": "orders@example.com",
"from_name": "Example Orders",
"nickname": "Orders",
"reply_to": "orders@example.com",
"reply_to_name": "Example Orders",
"state": "CA",
"zip": "94105"
},
"properties": {
"address": {
"maxLength": 100,
"type": "string"
},
"address2": {
"maxLength": 100,
"type": "string"
},
"city": {
"maxLength": 150,
"type": "string"
},
"country": {
"maxLength": 100,
"type": "string"
},
"from_email": {
"format": "email",
"maxLength": 256,
"type": "string"
},
"from_name": {
"maxLength": 256,
"type": "string"
},
"nickname": {
"maxLength": 100,
"type": "string"
},
"reply_to": {
"format": "email",
"maxLength": 256,
"type": "string"
},
"reply_to_name": {
"maxLength": 256,
"type": "string"
},
"state": {
"maxLength": 2,
"type": "string"
},
"zip": {
"maxLength": 10,
"type": "string"
}
},
"required": [
"nickname",
"from_email",
"reply_to"
],
"title": "Verified Sender Request Schema",
"type": "object",
"x-stoplight": {
"id": "verified-sender-request-schema",
"name": "Verified Sender Request Schema",
"public": true
}
},
"verified-sender-response-schema": {
"example": {
"address": "1234 Fake St.",
"address2": "PO Box 1234",
"city": "San Francisco",
"country": "USA",
"from_email": "orders@example.com",
"from_name": "Example Orders",
"id": 1234,
"locked": false,
"nickname": "Example Orders",
"reply_to": "orders@example.com",
"reply_to_name": "Example Orders",
"state": "CA",
"verified": true,
"zip": "94105"
},
"properties": {
"address": {
"type": "string"
},
"address2": {
"type": "string"
},
"city": {
"type": "string"
},
"country": {
"type": "string"
},
"from_email": {
"type": "string"
},
"from_name": {
"type": "string"
},
"id": {
"type": "integer"
},
"locked": {
"type": "boolean"
},
"nickname": {
"type": "string"
},
"reply_to": {
"type": "string"
},
"reply_to_name": {
"type": "string"
},
"state": {
"type": "string"
},
"verified": {
"type": "boolean"
},
"zip": {
"type": "string"
}
},
"title": "Verified Sender Response Schema",
"type": "object",
"x-stoplight": {
"id": "verified-sender-response-schema",
"name": "Verified Sender Response Schema",
"public": true
}
},
"webhook": {
"properties": {
"nonce": {
"description": "The one time nonce to use when \"signature\" is \"hmac-sha1\"",
"maxLength": 32,
"minLength": 8,
"type": "string"
},
"url": {
"description": "The URL to invoke in the webhook",
"type": "string"
}
},
"required": [
"url",
"nonce"
],
"title": "webhook",
"type": "object",
"x-stoplight": {
"id": "webhook",
"name": "webhook",
"public": true
}
},
"webhooks-event-webhook-request": {
"properties": {
"bounce": {
"description": "Receiving server could not or would not accept message.",
"type": "boolean"
},
"click": {
"description": "Recipient clicked on a link within the message. You need to enable Click Tracking for getting this type of event.",
"type": "boolean"
},
"deferred": {
"description": "Recipient's email server temporarily rejected message.",
"type": "boolean"
},
"delivered": {
"description": "Message has been successfully delivered to the receiving server.",
"type": "boolean"
},
"dropped": {
"description": "You may see the following drop reasons: Invalid SMTPAPI header, Spam Content (if spam checker app enabled), Unsubscribed Address, Bounced Address, Spam Reporting Address, Invalid, Recipient List over Package Quota",
"type": "boolean"
},
"enabled": {
"description": "Indicates if the event webhook is enabled.",
"type": "boolean"
},
"group_resubscribe": {
"description": "Recipient resubscribes to specific group by updating preferences. You need to enable Subscription Tracking for getting this type of event.",
"type": "boolean"
},
"group_unsubscribe": {
"description": "Recipient unsubscribe from specific group, by either direct link or updating preferences. You need to enable Subscription Tracking for getting this type of event.",
"type": "boolean"
},
"oauth_client_id": {
"description": "The client ID Twilio SendGrid sends to your OAuth server or service provider to generate an OAuth access token. When passing data in this field, you must also include the oauth_token_url field.",
"type": "string"
},
"oauth_token_url": {
"description": "The URL where Twilio SendGrid sends the Client ID and Client Secret to generate an access token. This should be your OAuth server or service provider. When passing data in this field, you must also include the oauth_client_id field.",
"type": "string"
},
"open": {
"description": "Recipient has opened the HTML message. You need to enable Open Tracking for getting this type of event.",
"type": "boolean"
},
"processed": {
"description": "Message has been received and is ready to be delivered.",
"type": "boolean"
},
"spam_report": {
"description": "Recipient marked a message as spam.",
"type": "boolean"
},
"unsubscribe": {
"description": "Recipient clicked on message's subscription management link. You need to enable Subscription Tracking for getting this type of event.",
"type": "boolean"
},
"url": {
"description": "The URL that you want the event webhook to POST to.",
"type": "string"
}
},
"required": [
"enabled",
"url",
"group_resubscribe",
"delivered",
"group_unsubscribe",
"spam_report",
"bounce",
"deferred",
"unsubscribe",
"processed",
"open",
"click",
"dropped"
],
"title": "Webhooks: Event Webhook Request",
"type": "object",
"x-stoplight": {
"id": "webhooks-event-webhook-request",
"name": "Webhooks: Event Webhook Request",
"public": true
}
}
},
"securitySchemes": {
"Authorization": {
"in": "header",
"name": "Authorization",
"type": "apiKey"
}
}
},
"info": {
"description": "The Beta endpoints for the new Email Activity APIs - functionality is subject to change without notice. You may not have access to this Beta endpoint.\n\nEmail Activity offers filtering and search by event type for two days worth of data. There is an optional add-on to store 60 days worth of data. This add-on also gives you access to the ability to download a CSV of the 60 days worth of email event data. The Beta endpoints for the new Email Activity APIs - functionality is subject to change without notice. You may not have access to this Beta endpoint.\n\nEmail Activity offers filtering and search by event type for two days worth of data. There is an optional add-on to store 60 days worth of data. This add-on also gives you access to the ability to download a CSV of the 60 days worth of email event data.",
"title": "Email Activity (beta)",
"version": ""
},
"openapi": "3.1.0",
"paths": {
"/access_settings/activity": {
"get": {
"description": "**This endpoint allows you to retrieve a list of all of the IP addresses that recently attempted to access your account either through the User Interface or the API.**",
"operationId": "GET_access_settings-activity",
"parameters": [
{
"description": "Limits the number of IPs to return.",
"in": "query",
"name": "limit",
"schema": {
"default": 20,
"type": "integer"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"result": [
{
"allowed": false,
"auth_method": "Web",
"first_at": 1444087966,
"ip": "1.1.1.1",
"last_at": 1444406672,
"location": "Australia"
},
{
"allowed": false,
"auth_method": "Web",
"first_at": 1444087505,
"ip": "1.2.3.48",
"last_at": 1444087505,
"location": "Mukilteo, Washington"
}
]
}
}
},
"schema": {
"properties": {
"result": {
"description": "An array containing the IPs that recently attempted to access your account.",
"items": {
"properties": {
"allowed": {
"description": "Indicates if the IP address was granted access to the account.",
"type": "boolean"
},
"auth_method": {
"description": "The authentication method used when attempting access.",
"type": "string"
},
"first_at": {
"description": "A Unix timestamp indicating when the first access attempt was made.",
"type": "integer"
},
"ip": {
"description": "The IP addressed used during the access attempt.",
"type": "string"
},
"last_at": {
"description": "A Unix timestamp indicating when the most recent access attempt was made",
"type": "integer"
},
"location": {
"description": "The geographic location from which the access attempt originated.",
"type": "string"
}
},
"required": [
"allowed",
"auth_method",
"first_at",
"ip",
"last_at",
"location"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"result"
],
"type": "object"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve all recent access attempts",
"tags": [
"IP Access Management"
],
"x-stoplight": {
"id": "GET_accesssettings-activity",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/access_settings/whitelist": {
"delete": {
"description": "**This endpoint allows you to remove one or more IP addresses from your list of allowed addresses.**\n\nTo remove one or more IP addresses, pass this endpoint an array containing the ID(s) associated with the IP(s) you intend to remove. You can retrieve the IDs associated with your allowed IP addresses using the \"Retrieve a list of currently allowed IPs\" endpoint.\n\nIt is possible to remove your own IP address, which will block access to your account. You will need to submit a [support ticket](https://sendgrid.com/docs/ui/account-and-settings/support/) if this happens. For this reason, it is important to double check that you are removing only the IPs you intend to remove when using this endpoint.",
"operationId": "DELETE_access_settings-whitelist",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"ids": [
1,
2,
3
]
},
"properties": {
"ids": {
"description": "An array of the IDs of the IP address that you want to remove from your allow list.",
"items": {
"type": "integer"
},
"type": "array"
}
},
"type": "object"
}
}
}
},
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"properties": {},
"type": "object"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Remove one or more IPs from the allow list",
"tags": [
"IP Access Management"
],
"x-stoplight": {
"id": "DELETE_accesssettings-whitelist",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve a list of IP addresses that are currently allowed to access your account.**\n\nEach IP address returned to you will have `created_at` and `updated_at` dates. Each IP will also be associated with an `id` that can be used to remove the address from your allow list.",
"operationId": "GET_access_settings-whitelist",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"result": [
{
"created_at": 1441824715,
"id": 1,
"ip": "192.168.1.1/32",
"updated_at": 1441824715
},
{
"created_at": 1441824715,
"id": 2,
"ip": "192.168.1.2/32",
"updated_at": 1441824715
},
{
"created_at": 1441824715,
"id": 3,
"ip": "192.168.1.3/32",
"updated_at": 1441824715
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/ip-access-response"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve a list of currently allowed IPs",
"tags": [
"IP Access Management"
],
"x-stoplight": {
"id": "GET_accesssettings-whitelist",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"post": {
"description": "**This endpoint allows you to add one or more allowed IP addresses.**\n\nTo allow one or more IP addresses, pass them to this endpoint in an array. Once an IP address is added to your allow list, it will be assigned an `id` that can be used to remove the address. You can retrieve the ID associated with an IP using the \"Retrieve a list of currently allowed IPs\" endpoint.",
"operationId": "POST_access_settings-whitelist",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"ips": [
{
"ip": "192.168.1.1"
},
{
"ip": "192.*.*.*"
},
{
"ip": "192.168.1.3/32"
}
]
},
"properties": {
"ips": {
"description": "An array containing the IP(s) you want to allow.",
"items": {
"properties": {
"ip": {
"description": "An IP address that you want to allow.",
"type": "string"
}
},
"required": [
"ip"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"ips"
],
"type": "object"
}
}
}
},
"responses": {
"201": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"result": [
{
"created_at": 1441824715,
"id": 1,
"ip": "192.168.1.1/32",
"updated_at": 1441824715
},
{
"created_at": 1441824715,
"id": 2,
"ip": "192.0.0.0/8",
"updated_at": 1441824715
},
{
"created_at": 1441824715,
"id": 3,
"ip": "192.168.1.3/32",
"updated_at": 1441824715
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/ip-access-response"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Add one or more IPs to the allow list",
"tags": [
"IP Access Management"
],
"x-stoplight": {
"id": "POST_accesssettings-whitelist",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/access_settings/whitelist/{rule_id}": {
"delete": {
"description": "**This endpoint allows you to remove a specific IP address from your list of allowed addresses.**\n\nWhen removing a specific IP address from your list, you must include the ID in your call. You can retrieve the IDs associated with your allowed IP addresses using the \"Retrieve a list of currently allowed IPs\" endpoint.",
"operationId": "DELETE_access_settings-whitelist-rule_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"properties": {},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Remove a specific IP from the allowed list",
"tags": [
"IP Access Management"
],
"x-stoplight": {
"id": "DELETE_accesssettings-whitelist-ruleid",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retreive a specific IP address that has been allowed to access your account.**\n\nYou must include the ID for the specific IP address you want to retrieve in your call. You can retrieve the IDs associated with your allowed IP addresses using the \"Retrieve a list of currently allowed IPs\" endpoint.",
"operationId": "GET_access_settings-whitelist-rule_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"created_at": 1441824715,
"id": 1,
"ip": "192.168.1.1",
"updated_at": 1441824715
}
}
},
"schema": {
"$ref": "#/components/schemas/ip-access-response"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve a specific allowed IP",
"tags": [
"IP Access Management"
],
"x-stoplight": {
"id": "GET_accesssettings-whitelist-ruleid",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"description": "The ID of the allowed IP address that you want to retrieve.",
"in": "path",
"name": "rule_id",
"required": true,
"schema": {
"type": "string"
}
}
]
},
"/alerts": {
"get": {
"description": "**This endpoint allows you to retrieve all of your alerts.**\n\nAlerts allow you to specify an email address to receive notifications regarding your email usage or statistics. \n* Usage alerts allow you to set the threshold at which an alert will be sent.\n* Stats notifications allow you to set how frequently you would like to receive email statistics reports. For example, \"daily\", \"weekly\", or \"monthly\".\n\nFor more information about alerts, please see our [Alerts documentation](https://sendgrid.com/docs/ui/account-and-settings/alerts/).",
"operationId": "GET_alerts",
"parameters": [
{
"in": "header",
"name": "Authorization",
"schema": {
"type": "string"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"created_at": 1451498784,
"email_to": "example1@example.com",
"id": 46,
"percentage": 90,
"type": "usage_limit",
"updated_at": 1451498784
},
{
"created_at": 1451498812,
"email_to": "example2@example.com",
"frequency": "monthly",
"id": 47,
"type": "stats_notification",
"updated_at": 1451498812
},
{
"created_at": 1451520930,
"email_to": "example3@example.com",
"frequency": "daily",
"id": 48,
"type": "stats_notification",
"updated_at": 1451520930
}
]
}
},
"schema": {
"description": "The list of alerts.",
"items": {
"properties": {
"created_at": {
"description": "A Unix timestamp indicating when the alert was created.",
"type": "integer"
},
"email_to": {
"description": "The email address that the alert will be sent to.",
"type": "string"
},
"frequency": {
"description": "If the alert is of type stats_notification, this indicates how frequently the stats notifications will be sent. For example, \"daily\", \"weekly\", or \"monthly\".",
"type": "string"
},
"id": {
"description": "The ID of the alert.",
"type": "integer"
},
"percentage": {
"description": "If the alert is of type usage_limit, this indicates the percentage of email usage that must be reached before the alert will be sent.",
"type": "integer"
},
"type": {
"description": "The type of alert.",
"enum": [
"usage_limit",
"stats_notification"
],
"type": "string"
},
"updated_at": {
"description": "A Unix timestamp indicating when the alert was last modified.",
"type": "integer"
}
},
"required": [
"created_at",
"email_to",
"id",
"type"
],
"type": "object"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve all alerts",
"tags": [
"Alerts"
],
"x-stoplight": {
"beforeScript": "function (ctx, request) {\n // Your javascript code here.+\n request.header.set('User-Agent', 'sendgrid/stoplightLocs');\n}",
"id": "GET_alerts",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"post": {
"description": "**This endpoint allows you to create a new alert.**\n\nAlerts allow you to specify an email address to receive notifications regarding your email usage or statistics. There are two types of alerts that can be created with this endpoint:\n\n* `usage_limit` allows you to set the threshold at which an alert will be sent.\n* `stats_notification` allows you to set how frequently you would like to receive email statistics reports. For example, \"daily\", \"weekly\", or \"monthly\".\n\nFor more information about alerts, please see our [Alerts documentation](https://sendgrid.com/docs/ui/account-and-settings/alerts/).",
"operationId": "POST_alerts",
"parameters": [
{
"in": "header",
"name": "Authorization",
"schema": {
"type": "string"
}
},
{
"in": "header",
"name": "on-behalf-of",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"email_to": "example@example.com",
"frequency": "daily",
"type": "stats_notification"
},
"properties": {
"email_to": {
"description": "The email address the alert will be sent to.\nExample: test@example.com",
"format": "email",
"nullable": true,
"type": "string"
},
"frequency": {
"description": "Required for stats_notification. How frequently the alert will be sent.\nExample: daily",
"type": "string"
},
"percentage": {
"description": "Required for usage_alert. When this usage threshold is reached, the alert will be sent.\nExample: 90",
"type": "integer"
},
"type": {
"description": "The type of alert you want to create. Can be either usage_limit or stats_notification.\nExample: usage_limit",
"enum": [
"stats_notification",
"usage_limit"
],
"type": "string"
}
},
"required": [
"type",
"email_to"
],
"type": "object"
}
}
}
},
"responses": {
"201": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"created_at": 1451520930,
"email_to": "test@example.com",
"frequency": "daily",
"id": 48,
"type": "stats_notification",
"updated_at": 1451520930
}
}
},
"schema": {
"properties": {
"created_at": {
"description": "A Unix timestamp indicating when the alert was created.",
"type": "integer"
},
"email_to": {
"description": "The email address that the alert will be sent to.",
"format": "email",
"type": "string"
},
"frequency": {
"description": "If the alert is of type stats_notification, this indicates how frequently the stats notifications will be sent. For example, \"daily\", \"weekly\", or \"monthly\".",
"type": "string"
},
"id": {
"description": "The ID of the alert.",
"type": "integer"
},
"percentage": {
"description": "\"If the alert is of type usage_limit, this indicates the percentage of email usage that must be reached before the alert will be sent.",
"type": "integer"
},
"type": {
"description": "The type of alert.",
"type": "string"
},
"updated_at": {
"description": "A Unix timestamp indicating when the alert was last modified.",
"type": "integer"
}
},
"required": [
"created_at",
"email_to",
"id",
"type",
"updated_at"
],
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"properties": {
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Create a new Alert",
"tags": [
"Alerts"
],
"x-stoplight": {
"id": "POST_alerts",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/alerts/{alert_id}": {
"delete": {
"description": "**This endpoint allows you to delete an alert.**\n\nAlerts allow you to specify an email address to receive notifications regarding your email usage or statistics. \n* Usage alerts allow you to set the threshold at which an alert will be sent.\n* Stats notifications allow you to set how frequently you would like to receive email statistics reports. For example, \"daily\", \"weekly\", or \"monthly\".\n\nFor more information about alerts, please see our [Alerts documentation](https://sendgrid.com/docs/ui/account-and-settings/alerts/).",
"operationId": "DELETE_alerts-alert_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"properties": {},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete an alert",
"tags": [
"Alerts"
],
"x-stoplight": {
"id": "DELETE_alerts-alertid",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve a specific alert.**\n\nAlerts allow you to specify an email address to receive notifications regarding your email usage or statistics. \n* Usage alerts allow you to set the threshold at which an alert will be sent.\n* Stats notifications allow you to set how frequently you would like to receive email statistics reports. For example, \"daily\", \"weekly\", or \"monthly\".\n\nFor more information about alerts, please see our [Alerts documentation](https://sendgrid.com/docs/ui/account-and-settings/alerts/).",
"operationId": "GET_alerts-alert_id",
"parameters": [
{
"in": "header",
"name": "Authorization",
"schema": {
"type": "string"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"created_at": 1451520930,
"email_to": "example@example.com",
"frequency": "daily",
"id": 48,
"type": "stats_notification",
"updated_at": 1451520930
}
}
},
"schema": {
"properties": {
"created_at": {
"description": "A Unix timestamp indicating when the alert was created.",
"type": "integer"
},
"email_to": {
"description": "The email address that the alert will be sent to.",
"type": "string"
},
"frequency": {
"description": "If the alert is of type stats_notification, this indicates how frequently the stats notifications will be sent. For example: \"daily\", \"weekly\", or \"monthly\".",
"type": "string"
},
"id": {
"description": "The ID of the alert.",
"type": "integer"
},
"percentage": {
"description": "If the alert is of type usage_limit, this indicates the percentage of email usage that must be reached before the alert will be sent.",
"type": "integer"
},
"type": {
"description": "The type of alert.",
"enum": [
"usage_alert",
"stats_notification"
],
"type": "string"
},
"updated_at": {
"description": "A Unix timestamp indicating when the alert was last modified.",
"type": "integer"
}
},
"required": [
"created_at",
"email_to",
"id",
"type",
"updated_at"
],
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve a specific alert",
"tags": [
"Alerts"
],
"x-stoplight": {
"id": "GET_alerts-alertid",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"description": "The ID of the alert you would like to retrieve.",
"in": "path",
"name": "alert_id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"patch": {
"description": "**This endpoint allows you to update an alert.**\n\nAlerts allow you to specify an email address to receive notifications regarding your email usage or statistics. \n* Usage alerts allow you to set the threshold at which an alert will be sent.\n* Stats notifications allow you to set how frequently you would like to receive email statistics reports. For example, \"daily\", \"weekly\", or \"monthly\".\n\nFor more information about alerts, please see our [Alerts documentation](https://sendgrid.com/docs/ui/account-and-settings/alerts/).",
"operationId": "PATCH_alerts-alert_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"email_to": "example@example.com"
},
"properties": {
"email_to": {
"description": "The new email address you want your alert to be sent to.\nExample: test@example.com",
"type": "string"
},
"frequency": {
"description": "The new frequency at which to send the stats_notification alert.\nExample: monthly",
"type": "string"
},
"percentage": {
"description": "The new percentage threshold at which the usage_limit alert will be sent.\nExample: 90",
"type": "integer"
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"created_at": 1451520930,
"email_to": "example@example.com",
"frequency": "daily",
"id": 48,
"type": "stats_notification",
"updated_at": 1451522691
}
}
},
"schema": {
"properties": {
"created_at": {
"description": "A Unix timestamp indicating when the alert was created.",
"type": "integer"
},
"email_to": {
"description": "The email address that the alert will be sent to.",
"type": "string"
},
"frequency": {
"description": "If the alert is of type stats_notification, this indicates how frequently the stats notifications will be sent. For example: \"daily\", \"weekly\", or \"monthly\".",
"type": "string"
},
"id": {
"description": "The ID of the alert.",
"type": "integer"
},
"percentage": {
"description": "If the alert is of type usage_limit, this indicates the percentage of email usage that must be reached before the alert will be sent.",
"type": "integer"
},
"type": {
"description": "The type of alert.",
"enum": [
"usage_alert",
"stats_notification"
],
"type": "string"
},
"updated_at": {
"description": "A Unix timestamp indicating when the alert was last modified.",
"type": "integer"
}
},
"required": [
"created_at",
"email_to",
"id",
"type",
"updated_at"
],
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update an alert",
"tags": [
"Alerts"
],
"x-stoplight": {
"id": "PATCH_alerts-alertid",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/api_keys": {
"get": {
"description": "**This endpoint allows you to retrieve all API Keys that belong to the authenticated user.**\n\nA successful response from this API will include all available API keys' names and IDs.\n\nFor security reasons, there is not a way to retrieve the key itself after it's created. If you lose your API key, you must create a new one. Only the \"Create API keys\" endpoint will return a key to you and only at the time of creation.\n\nAn `api_key_id` can be used to update or delete the key, as well as retrieve the key's details, such as its scopes.",
"operationId": "GET_api_keys",
"parameters": [
{
"in": "query",
"name": "limit",
"schema": {
"type": "integer"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"result": [
{
"api_key_id": "some-apikey-id",
"name": "API Key Name"
},
{
"api_key_id": "another-apikey-id",
"name": "API Key Name 2"
}
]
}
}
},
"schema": {
"properties": {
"result": {
"items": {
"$ref": "#/components/schemas/api_key_name_id"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve all API Keys belonging to the authenticated user",
"tags": [
"API Keys"
],
"x-stoplight": {
"id": "GET_apikeys",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"post": {
"description": "**This endpoint allows you to create a new API Key for the user.**\n\nTo create your initial SendGrid API Key, you should [use the SendGrid App](https://app.sendgrid.com/settings/api_keys). Once you have created a first key with scopes to manage additional API keys, you can use this API for all other key management.\n\n> There is a limit of 100 API Keys on your account.\n\nA JSON request body containing a `name` property is required when making requests to this endpoint. If the number of maximum keys, 100, is reached, a `403` status will be returned.\n\nThough the `name` field is required, it does not need to be unique. A unique API key ID will be generated for each key you create and returned in the response body.\n\nIt is not necessary to pass a `scopes` field to the API when creating a key, but you should be aware that omitting the `scopes` field from your request will create a key with \"Full Access\" permissions by default.\n\nSee the [API Key Permissions List](https://sendgrid.api-docs.io/v3.0/how-to-use-the-sendgrid-v3-api/api-authorization) for all available scopes. An API key's scopes can be updated after creation using the \"Update API keys\" endpoint.",
"operationId": "create-api-keys",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"name": "My API Key",
"scopes": [
"mail.send",
"alerts.create",
"alerts.read"
]
},
"properties": {
"name": {
"description": "The name you will use to describe this API Key.",
"type": "string"
},
"scopes": {
"description": "The individual permissions that you are giving to this API Key.",
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"name"
],
"type": "object"
}
}
}
},
"responses": {
"201": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"api_key": "SG.xxxxxxxx.yyyyyyyy",
"api_key_id": "xxxxxxxx",
"name": "My API Key",
"scopes": [
"mail.send",
"alerts.create",
"alerts.read"
]
}
}
},
"schema": {
"properties": {
"api_key": {
"type": "string"
},
"api_key_id": {
"type": "string"
},
"name": {
"type": "string"
},
"scopes": {
"items": {
"type": "string"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_apiKeysErrors_400"
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_apiKeysErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_apiKeysErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Create API keys",
"tags": [
"API Keys"
],
"x-stoplight": {
"id": "create-api-keys",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 201
},
"public": true
}
}
},
"/api_keys/{api_key_id}": {
"delete": {
"description": "**This endpoint allows you to revoke an existing API Key using an `api_key_id`**\n\nAuthentications using a revoked API Key will fail after after some small propogation delay. If the API Key ID does not exist, a `404` status will be returned.",
"operationId": "DELETE_api_keys-api_key_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"204": {
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_apiKeysErrors_400"
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete API keys",
"tags": [
"API Keys"
],
"x-stoplight": {
"id": "DELETE_apikeys-apikeyid",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve a single API key using an `api_key_id`.**\n\nThe endpoint will return a key's name, ID, and scopes. If the API Key ID does not, exist a `404` status will be returned.\n\nSee the [API Key Permissions List](https://sendgrid.api-docs.io/v3.0/how-to-use-the-sendgrid-v3-api/api-authorization) for all available scopes. An API key's scopes can be updated after creation using the \"Update API keys\" endpoint.",
"operationId": "GET_api_keys-api_key_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"result": [
{
"api_key_id": "some-apikey-id",
"name": "API Key Name"
},
{
"api_key_id": "another-apikey-id",
"name": "API Key Name 2"
}
]
}
}
},
"schema": {
"properties": {
"result": {
"items": {
"$ref": "#/components/schemas/api_key_name_id_scopes"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_apiKeysErrors_400"
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_apiKeysErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_apiKeysErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve an existing API Key",
"tags": [
"API Keys"
],
"x-stoplight": {
"id": "GET_apikeys-apikeyid",
"mock": {
"dynamic": false
},
"public": true
}
},
"parameters": [
{
"description": "",
"in": "path",
"name": "api_key_id",
"required": true,
"schema": {
"type": "string"
}
}
],
"patch": {
"description": "**This endpoint allows you to update the name of an existing API Key.**\n\nYou must pass this endpoint a JSON request body with a `name` property, which will be used to rename the key associated with the `api_key_id` passed in the URL.",
"operationId": "PATCH_api_keys-api_key_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"name": "A New Hope"
},
"properties": {
"name": {
"description": "The new name of the API Key.",
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"api_key_id": "qfTQ6KG0QBiwWdJ0-pCLCA",
"name": "A New Hope"
}
}
},
"schema": {
"$ref": "#/components/schemas/api_key_name_id"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_apiKeysErrors_400"
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update API key name",
"tags": [
"API Keys"
],
"x-stoplight": {
"id": "PATCH_apikeys-apikeyid",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"put": {
"description": "**This endpoint allows you to update the name and scopes of a given API key.**\n\nYou must pass this endpoint a JSON request body with a `name` field and a `scopes` array containing at least one scope. The `name` and `scopes` fields will be used to update the key associated with the `api_key_id` in the request URL.\n\nIf you need to update a key's scopes only, pass the `name` field with the key's existing name; the `name` will not be modified. If you need to update a key's name only, use the \"Update API key name\" endpoint.\n\nSee the [API Key Permissions List](https://sendgrid.api-docs.io/v3.0/how-to-use-the-sendgrid-v3-api/api-authorization) for all available scopes.",
"operationId": "PUT_api_keys-api_key_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"name": "Profiles key",
"scopes": [
"user.profile.read",
"user.profile.update"
]
},
"properties": {
"name": {
"type": "string"
},
"scopes": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"name"
],
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"api_key_id": "qfTQ6KG0QBiwWdJ0-pCLCA",
"name": "Profiles Key",
"scopes": [
"user.profile.read",
"user.profile.update"
]
}
}
},
"schema": {
"$ref": "#/components/schemas/api_key_name_id_scopes"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_apiKeysErrors_400"
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update API key name and scopes",
"tags": [
"API Keys"
],
"x-stoplight": {
"id": "PUT_apikeys-apikeyid",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/asm/groups": {
"get": {
"description": "**This endpoint allows you to retrieve a list of all suppression groups created by this user.**\n\nThis endpoint can also return information for multiple group IDs that you include in your request. To add a group ID to your request, simply append `?id=123456&id=123456`, with the appropriate group IDs.",
"operationId": "GET_asm-groups",
"parameters": [
{
"in": "query",
"name": "id",
"schema": {
"type": "integer"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"description": "An Unsubscribe Group",
"id": 1234,
"is_default": true,
"last_email_sent_at": null,
"name": "Unsubscribe Group",
"unsubscribes": 1234
},
{
"description": "An Unsubscribe Group",
"id": 1234,
"is_default": true,
"last_email_sent_at": null,
"name": "Unsubscribe Group",
"unsubscribes": 1234
}
]
}
},
"schema": {
"items": {
"$ref": "#/components/schemas/suppression_group"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve all suppression groups associated with the user.",
"tags": [
"Suppressions - Unsubscribe Groups"
],
"x-stoplight": {
"id": "GET_asm-groups",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"post": {
"description": "**This endpoint allows you to create a new suppression group.**\n\nTo add an email address to the suppression group, [create a Suppression](https://sendgrid.api-docs.io/v3.0/suppressions-suppressions/add-suppressions-to-a-suppression-group).",
"operationId": "POST_asm-groups",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/suppression-group-request-base"
}
}
}
},
"responses": {
"201": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"description": "Suggestions for products our users might like.",
"id": 103,
"is_default": false,
"name": "Product Suggestions"
}
}
},
"schema": {
"properties": {
"description": {
"description": "A brief description of the suppression group.",
"type": "string"
},
"id": {
"description": "The ID of the suppression group.",
"type": "integer"
},
"is_default": {
"description": "Indicates if this is the default suppression group.",
"type": "boolean"
},
"name": {
"description": "The name of the suppression group.",
"type": "string"
}
},
"required": [
"id",
"name",
"description",
"is_default"
],
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Create a new suppression group",
"tags": [
"Suppressions - Unsubscribe Groups"
],
"x-stoplight": {
"id": "POST_asm-groups",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/asm/groups/{group_id}": {
"delete": {
"description": "**This endpoint allows you to delete a suppression group.**\n\nIf a recipient uses the \"one-click unsubscribe\" option on an email associated with a deleted group, that recipient will be added to the global suppression list.\n\nDeleting a suppression group will remove the suppression, meaning email will once again be sent to the previously suppressed addresses. This should be avoided unless a recipient indicates they wish to receive email from you again. You can use our [bypass filters](https://sendgrid.com/docs/ui/sending-email/index-suppressions/#bypass-suppressions) to deliver messages to otherwise suppressed addresses when exceptions are required.",
"operationId": "DELETE_asm-groups-group_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete a Suppression Group",
"tags": [
"Suppressions - Unsubscribe Groups"
],
"x-stoplight": {
"id": "DELETE_asm-groups-groupid",
"mock": {
"dynamic": false,
"statusCode": 204
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve a single suppression group.**",
"operationId": "GET_asm-groups-group_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"description": "Our monthly newsletter.",
"id": 100,
"is_default": true,
"last_email_sent_at": null,
"name": "Newsletters",
"unsubscribes": 400
}
}
},
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/suppression-group-request-base"
},
{
"properties": {
"id": {
"description": "The ID of the suppression group.",
"type": "integer"
},
"last_email_sent_at": {
"nullable": true,
"type": "string"
},
"unsubscribes": {
"description": "The number of unsubscribes, or suppressions, in this group.",
"type": "integer"
}
},
"required": [
"id"
],
"type": "object"
}
]
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Get information on a single suppression group.",
"tags": [
"Suppressions - Unsubscribe Groups"
],
"x-stoplight": {
"id": "GET_asm-groups-groupid",
"mock": {
"dynamic": false
},
"public": true
}
},
"parameters": [
{
"description": "The ID of the suppression group you would like to retrieve.",
"in": "path",
"name": "group_id",
"required": true,
"schema": {
"type": "string"
}
}
],
"patch": {
"description": "**This endpoint allows you to update or change a suppression group.**",
"operationId": "PATCH_asm-groups-group_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/suppression-group-request-base"
}
],
"example": {
"description": "Suggestions for items our users might like.",
"id": 103,
"name": "Item Suggestions"
}
}
}
}
},
"responses": {
"201": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"description": "Suggestions for items our users might like.",
"id": 103,
"name": "Item Suggestions"
}
}
},
"schema": {
"$ref": "#/components/schemas/suppression_group"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update a suppression group.",
"tags": [
"Suppressions - Unsubscribe Groups"
],
"x-stoplight": {
"id": "PATCH_asm-groups-groupid",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/asm/groups/{group_id}/suppressions": {
"get": {
"description": "**This endpoint allows you to retrieve all suppressed email addresses belonging to the given group.**",
"operationId": "GET_asm-groups-group_id-suppressions",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
"example@example.com",
"example2@example.com"
]
}
},
"schema": {
"description": "The list of email addresses belonging to the given suppression group.",
"items": {
"format": "email",
"type": "string"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve all suppressions for a suppression group",
"tags": [
"Suppressions - Suppressions"
],
"x-stoplight": {
"id": "GET_asm-groups-groupid-suppressions",
"mock": {
"dynamic": false
},
"public": true
}
},
"parameters": [
{
"description": "The id of the unsubscribe group that you are adding suppressions to.",
"in": "path",
"name": "group_id",
"required": true,
"schema": {
"type": "string"
}
}
],
"post": {
"description": "**This endpoint allows you to add email addresses to an unsubscribe group.**\n\nIf you attempt to add suppressions to a group that has been deleted or does not exist, the suppressions will be added to the global suppressions list.",
"operationId": "POST_asm-groups-group_id-suppressions",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"$ref": "#/components/requestBodies/suppressions-request"
},
"responses": {
"201": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"recipient_emails": [
"test1@example.com",
"test2@example.com"
]
}
}
},
"schema": {
"properties": {
"recipient_emails": {
"description": "The email addresses you added to the unsubscribe group",
"items": {
"format": "email",
"type": "string"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Add suppressions to a suppression group",
"tags": [
"Suppressions - Suppressions"
],
"x-stoplight": {
"id": "POST_asm-groups-groupid-suppressions",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/asm/groups/{group_id}/suppressions/search": {
"parameters": [
{
"description": "The ID of the suppression group that you would like to search.",
"in": "path",
"name": "group_id",
"required": true,
"schema": {
"type": "string"
}
}
],
"post": {
"description": "**This endpoint allows you to search a suppression group for multiple suppressions.**\n\nWhen given a list of email addresses and a group ID, this endpoint will only return the email addresses that have been unsubscribed from the given group.",
"operationId": "POST_asm-groups-group_id-suppressions-search",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"$ref": "#/components/requestBodies/suppressions-request"
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
"exists1@example.com",
"exists2@example.com"
]
}
},
"schema": {
"description": "The email address from your search that do exist in the suppression group.",
"items": {
"format": "email",
"type": "string"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Search for suppressions within a group",
"tags": [
"Suppressions - Suppressions"
],
"x-stoplight": {
"id": "POST_asm-groups-groupid-suppressions-search",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/asm/groups/{group_id}/suppressions/{email}": {
"delete": {
"description": "**This endpoint allows you to remove a suppressed email address from the given suppression group.**\n\nRemoving an address will remove the suppression, meaning email will once again be sent to the previously suppressed addresses. This should be avoided unless a recipient indicates they wish to receive email from you again. You can use our [bypass filters](https://sendgrid.com/docs/ui/sending-email/index-suppressions/#bypass-suppressions) to deliver messages to otherwise suppressed addresses when exceptions are required.",
"operationId": "DELETE_asm-groups-group_id-suppressions-email",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"nullable": true
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete a suppression from a suppression group",
"tags": [
"Suppressions - Suppressions"
],
"x-stoplight": {
"id": "DELETE_asm-groups-groupid-suppressions-email",
"mock": {
"dynamic": false,
"statusCode": 204
},
"public": true
}
},
"parameters": [
{
"description": "The id of the suppression group that you are removing an email address from.",
"in": "path",
"name": "group_id",
"required": true,
"schema": {
"type": "string"
}
},
{
"description": "The email address that you want to remove from the suppression group.",
"in": "path",
"name": "email",
"required": true,
"schema": {
"type": "string"
}
}
]
},
"/asm/suppressions": {
"get": {
"description": "**This endpoint allows you to retrieve a list of all suppressions.**",
"operationId": "GET_asm-suppressions",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"created_at": 1410986704,
"email": "test1@example.com",
"group_id": 1,
"group_name": "Weekly News"
},
{
"created_at": 1411493671,
"email": "test1@example.com",
"group_id": 2,
"group_name": "Daily News"
},
{
"created_at": 1411493671,
"email": "test2@example.com",
"group_id": 2,
"group_name": "Daily News"
}
]
}
},
"schema": {
"items": {
"properties": {
"created_at": {
"description": "A UNIX timestamp indicating when the suppression was created.",
"type": "integer"
},
"email": {
"description": "The email address that was suppressed.",
"type": "string"
},
"group_id": {
"description": "The id of the suppression group that this email address belongs to.",
"type": "integer"
},
"group_name": {
"description": "The name of the suppression group that this email address belongs to.",
"type": "string"
}
},
"required": [
"email",
"group_id",
"group_name",
"created_at"
],
"type": "object"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve all suppressions",
"tags": [
"Suppressions - Suppressions"
],
"x-stoplight": {
"id": "GET_asm-suppressions",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/asm/suppressions/global": {
"post": {
"description": "**This endpoint allows you to add one or more email addresses to the global suppressions group.**",
"operationId": "POST_asm-suppressions-global",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"$ref": "#/components/requestBodies/suppressions-request"
},
"responses": {
"201": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"recipient_emails": [
"test1@example.com",
"test2@example.com"
]
}
}
},
"schema": {
"properties": {
"recipient_emails": {
"description": "The email addresses that are globally suppressed",
"items": {
"format": "email",
"type": "string"
},
"type": "array"
}
},
"required": [
"recipient_emails"
],
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Add recipient addresses to the global suppression group.",
"tags": [
"Suppressions - Global Suppressions"
],
"x-stoplight": {
"id": "POST_asm-suppressions-global",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/asm/suppressions/global/{email}": {
"delete": {
"description": "**This endpoint allows you to remove an email address from the global suppressions group.**\n\nDeleting a suppression group will remove the suppression, meaning email will once again be sent to the previously suppressed addresses. This should be avoided unless a recipient indicates they wish to receive email from you again. You can use our [bypass filters](https://sendgrid.com/docs/ui/sending-email/index-suppressions/#bypass-suppressions) to deliver messages to otherwise suppressed addresses when exceptions are required.",
"operationId": "DELETE_asm-suppressions-global-email",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete a Global Suppression",
"tags": [
"Suppressions - Global Suppressions"
],
"x-stoplight": {
"id": "DELETE_asm-suppressions-global-email",
"mock": {
"dynamic": false
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve a global suppression. You can also use this endpoint to confirm if an email address is already globally suppresed.**\n\nIf the email address you include in the URL path parameter `{email}` is already globally suppressed, the response will include that email address. If the address you enter for `{email}` is not globally suppressed, an empty JSON object `{}` will be returned.",
"operationId": "GET_asm-suppressions-global-email",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"recipient_email": "test@example.com"
}
}
},
"schema": {
"properties": {
"recipient_email": {
"description": "The email address that is globally suppressed. This will be an empty object if the email address you included in your call is not globally suppressed.",
"format": "email",
"type": "string"
}
},
"required": [
"recipient_email"
],
"title": "Retrieve a Global Suppression response",
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve a Global Suppression",
"tags": [
"Suppressions - Global Suppressions"
],
"x-stoplight": {
"id": "GET_asm-suppressions-global-email",
"mock": {
"dynamic": false
},
"public": true
}
},
"parameters": [
{
"description": "The email address of the global suppression you want to retrieve. Or, if you want to check if an email address is on the global suppressions list, enter that email address here.",
"in": "path",
"name": "email",
"required": true,
"schema": {
"type": "string"
}
}
]
},
"/asm/suppressions/{email}": {
"get": {
"description": "**This endpoint returns a list of all groups from which the given email address has been unsubscribed.**",
"operationId": "GET_asm-suppressions-email",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"suppressions": [
{
"description": "Optional description.",
"id": 1,
"is_default": true,
"name": "Weekly News",
"suppressed": true
},
{
"description": "Some daily news.",
"id": 2,
"is_default": true,
"name": "Daily News",
"suppressed": true
},
{
"description": "An old group.",
"id": 2,
"is_default": false,
"name": "Old News",
"suppressed": false
}
]
}
}
},
"schema": {
"properties": {
"suppressions": {
"description": "The array of suppression groups.",
"items": {
"properties": {
"description": {
"description": "The description of the suppression group.",
"type": "string"
},
"id": {
"description": "The id of the suppression group.",
"type": "integer"
},
"is_default": {
"description": "Indicates if the suppression group is set as the default.",
"type": "boolean"
},
"name": {
"description": "The name of the suppression group.",
"type": "string"
},
"suppressed": {
"description": "Indicates if the given email address is suppressed for this group.",
"type": "boolean"
}
},
"required": [
"description",
"id",
"is_default",
"name",
"suppressed"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"suppressions"
],
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve all suppression groups for an email address",
"tags": [
"Suppressions - Suppressions"
],
"x-stoplight": {
"id": "GET_asm-suppressions-email",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"description": "The email address that you want to search suppression groups for.",
"in": "path",
"name": "email",
"required": true,
"schema": {
"type": "string"
}
}
]
},
"/browsers/stats": {
"get": {
"description": "**This endpoint allows you to retrieve your email statistics segmented by browser type.**\n\n**We only store up to 7 days of email activity in our database.** By default, 500 items will be returned per request via the Advanced Stats API endpoints.\n\nAdvanced Stats provide a more in-depth view of your email statistics and the actions taken by your recipients. You can segment these statistics by geographic location, device type, client type, browser, and mailbox provider. For more information about statistics, please see our [Statistics Overview](https://sendgrid.com/docs/ui/analytics-and-reporting/stats-overview/).",
"operationId": "GET_browsers-stats",
"parameters": [
{
"description": "The browsers to get statistics for. You can include up to 10 different browsers by including this parameter multiple times.",
"in": "query",
"name": "browsers",
"schema": {
"type": "string"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
},
{
"$ref": "#/components/parameters/trait_statsAdvancedQueryStringsLimitOffset_limit"
},
{
"$ref": "#/components/parameters/trait_statsAdvancedQueryStringsLimitOffset_offset"
},
{
"$ref": "#/components/parameters/trait_statsAdvancedQueryStringsLimitOffset_aggregated_by"
},
{
"$ref": "#/components/parameters/trait_statsAdvancedQueryStringsLimitOffset_start_date"
},
{
"$ref": "#/components/parameters/trait_statsAdvancedQueryStringsLimitOffset_end_date"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"date": "2014-10-01",
"stats": [
{
"metrics": {
"clicks": 0,
"unique_clicks": 0
},
"name": "Chrome",
"type": "browser"
},
{
"metrics": {
"clicks": 1,
"unique_clicks": 1
},
"name": "Firefox",
"type": "browser"
}
]
},
{
"date": "2014-10-02",
"stats": [
{
"metrics": {
"clicks": 0,
"unique_clicks": 0
},
"name": "Chrome",
"type": "browser"
},
{
"metrics": {
"clicks": 1,
"unique_clicks": 1
},
"name": "Firefox",
"type": "browser"
}
]
}
]
}
},
"schema": {
"items": {
"properties": {
"date": {
"description": "The date that the statistics were gathered.",
"type": "string"
},
"stats": {
"description": "The list of statistics.",
"items": {
"properties": {
"metrics": {
"$ref": "#/components/schemas/advanced_stats_clicks"
},
"name": {
"description": "The name of the specific segmentation.",
"type": "string"
},
"type": {
"description": "The type of segmentation.",
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve email statistics by browser.",
"tags": [
"Stats"
],
"x-stoplight": {
"id": "GET_browsers-stats",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/campaigns": {
"get": {
"description": "**This endpoint allows you to retrieve a list of all of your campaigns.**\n\nReturns campaigns in reverse order they were created (newest first).\n\nReturns an empty array if no campaigns exist.",
"operationId": "GET_campaigns",
"parameters": [
{
"description": "The number of results you would like to receive at a time.",
"in": "query",
"name": "limit",
"schema": {
"default": 10,
"type": "integer"
}
},
{
"description": "The index of the first campaign to return, where 0 is the first campaign.",
"in": "query",
"name": "offset",
"schema": {
"default": 0,
"type": "integer"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"result": [
{
"categories": [
"spring line"
],
"custom_unsubscribe_url": "",
"html_content": "Check out our spring line!
",
"id": 986724,
"ip_pool": "marketing",
"list_ids": [
110,
124
],
"plain_content": "Check out our spring line!",
"segment_ids": [
110
],
"sender_id": 124451,
"status": "Draft",
"subject": "New Products for Spring!",
"suppression_group_id": 42,
"title": "March Newsletter"
},
{
"categories": [
"winter line"
],
"custom_unsubscribe_url": "",
"html_content": "Last call for winter clothes!
",
"id": 986723,
"ip_pool": "marketing",
"list_ids": [
110,
124
],
"plain_content": "Last call for winter clothes!",
"segment_ids": [
110
],
"sender_id": 124451,
"status": "Sent",
"subject": "Final Winter Product Sale!",
"suppression_group_id": 42,
"title": "February Newsletter"
}
]
}
}
},
"schema": {
"properties": {
"result": {
"items": {
"$ref": "#/components/schemas/campaign_response"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve all Campaigns",
"tags": [
"Campaigns API"
],
"x-stoplight": {
"id": "GET_campaigns",
"mock": {
"dynamic": false
},
"public": true
}
},
"post": {
"description": "**This endpoint allows you to create a campaign.**\n\nIn order to send or schedule the campaign, you will be required to provide a subject, sender ID, content (we suggest both html and plain text), and at least one list or segment ID. This information is not required when you create a campaign.",
"operationId": "POST_campaigns",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/campaign_request"
}
}
}
},
"responses": {
"201": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"categories": [
"spring line"
],
"custom_unsubscribe_url": "",
"html_content": "Check out our spring line!
",
"id": 986724,
"ip_pool": "marketing",
"list_ids": [
110,
124
],
"plain_content": "Check out our spring line!",
"segment_ids": [
110
],
"sender_id": 124451,
"status": "Draft",
"subject": "New Products for Spring!",
"suppression_group_id": 42,
"title": "March Newsletter"
}
}
},
"schema": {
"$ref": "#/components/schemas/campaign_response"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": "title",
"message": "title can't be blank"
},
{
"field": "title",
"message": "title is too long (maximum is 100 characters)"
},
{
"field": "categories",
"message": "categories exceeds 10 category limit"
},
{
"field": "html_content",
"message": "html_content exceeds the 1MB limit"
},
{
"field": "plain_content",
"message": "plain_content exceeds the 1MB limit"
},
{
"field": "sender_id",
"message": "sender_id does not exist"
},
{
"field": "sender_id",
"message": "sender_id is not a verified sender identity"
},
{
"field": "list_ids",
"message": "list_ids do not all exist"
},
{
"field": "segment_ids",
"message": "segment_ids do not all exist"
},
{
"field": "ip_pool",
"message": "The ip pool you provided is invalid"
},
{
"field": "suppression_group_id",
"message": "suppression_group_id does not exist"
},
{
"field": "unsubscribes",
"message": "Either suppression_group_id or custom_unsubscribe_url may be set/used, but not both. Please remove one before setting the other."
},
{
"field": null,
"message": "The JSON you have submitted cannot be parsed."
},
{
"field": null,
"message": "You've reached your limit of 250 campaigns. Please delete one or more and try again."
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"title\": \"title can't be blank\"\n\"title\": \"title is too long (maximum is 100 characters)\"\n\"categories\": \"categories exceeds 10 category limit\"\n\"html_content\": \"html_content exceeds the 1MB limit\"\n\"plain_content\": \"plain_content exceeds the 1MB limit\"\n\"sender_id\": \"sender_id does not exist\"\n\"sender_id\": \"sender_id is not a verified sender identity\"\n\"list_ids\": \"list_ids do not all exist\"\n\"segment_ids\": \"segment_ids do not all exist\"\n\"ip_pool\": \"The ip pool you provided is invalid\"\n\"suppression_group_id\": \"suppression_group_id does not exist\"\n\"unsubscribes\": \"Either suppression_group_id or custom_unsubscribe_url may be set/used, but not both. Please remove one before setting the other.\"\n\"\": \"The JSON you have submitted cannot be parsed.\"\n\"\": \"You've reached your limit of 250 campaigns. Please delete one or more and try again.\""
},
"401": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Create a Campaign",
"tags": [
"Campaigns API"
],
"x-stoplight": {
"id": "POST_campaigns",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/campaigns/{campaign_id}": {
"delete": {
"description": "**This endpoint allows you to delete a specific campaign.**",
"operationId": "DELETE_campaigns-campaign_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"nullable": true
}
}
},
"description": ""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"type": "object"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": "\"\": \"not found\""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete a Campaign",
"tags": [
"Campaigns API"
],
"x-stoplight": {
"id": "DELETE_campaigns-campaignid",
"mock": {
"dynamic": false
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve a specific campaign.**",
"operationId": "GET_campaigns-campaign_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"categories": [
"spring line"
],
"custom_unsubscribe_url": "",
"html_content": "Check out our spring line!
",
"id": 986724,
"ip_pool": "marketing",
"list_ids": [
110
],
"plain_content": "Check out our spring line!",
"segment_ids": [
110
],
"sender_id": 124451,
"status": "Draft",
"subject": "New Products for Spring!",
"suppression_group_id": 42,
"title": "March Newsletter"
}
}
},
"schema": {
"properties": {
"categories": {
"items": {
"type": "string"
},
"type": "array"
},
"custom_unsubscribe_url": {
"type": "string"
},
"html_content": {
"type": "string"
},
"id": {
"type": "integer"
},
"ip_pool": {
"type": "string"
},
"list_ids": {
"items": {
"type": "integer"
},
"type": "array"
},
"plain_content": {
"type": "string"
},
"segment_ids": {
"items": {
"type": "integer"
},
"type": "array"
},
"sender_id": {
"type": "integer"
},
"status": {
"type": "string"
},
"subject": {
"type": "string"
},
"suppression_group_id": {
"type": "integer"
},
"title": {
"type": "string"
}
},
"type": "object"
}
}
},
"description": ""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"type": "object"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "not found"
}
]
}
}
},
"schema": {
"type": "object"
}
}
},
"description": "\"\": \"not found\""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve a single campaign",
"tags": [
"Campaigns API"
],
"x-stoplight": {
"id": "GET_campaigns-campaignid",
"mock": {
"dynamic": false
},
"public": true
}
},
"parameters": [
{
"description": "The id of the campaign you would like to retrieve.",
"in": "path",
"name": "campaign_id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"patch": {
"description": "**This endpoint allows you to update a specific campaign.**\n\nThis is especially useful if you only set up the campaign using POST /campaigns, but didn't set many of the parameters.",
"operationId": "PATCH_campaigns-campaign_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"categories": [
"summer line"
],
"html_content": "Check out our summer line!
",
"plain_content": "Check out our summer line!",
"subject": "New Products for Summer!",
"title": "May Newsletter"
},
"properties": {
"categories": {
"description": "The categories you want to tag on this campaign.",
"items": {
"type": "string"
},
"type": "array"
},
"html_content": {
"description": "The HTML content of this campaign.",
"type": "string"
},
"plain_content": {
"description": "The plain content of this campaign.",
"type": "string"
},
"subject": {
"description": "The subject line for your campaign.",
"type": "string"
},
"title": {
"description": "The title of the campaign.",
"type": "string"
}
},
"required": [
"title",
"subject",
"categories",
"html_content",
"plain_content"
],
"title": "Update a Campaign request",
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"categories": [
"summer line"
],
"custom_unsubscribe_url": "",
"html_content": "Check out our summer line!
",
"id": 986724,
"ip_pool": "marketing",
"list_ids": [
110,
124
],
"plain_content": "Check out our summer line!",
"segment_ids": [
110
],
"sender_id": 124451,
"status": "Draft",
"subject": "New Products for Summer!",
"suppression_group_id": 42,
"title": "May Newsletter"
}
}
},
"schema": {
"$ref": "#/components/schemas/campaign_response"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": "title",
"message": "title can't be blank"
},
{
"field": "title",
"message": "title is too long (maximum is 100 characters)"
},
{
"field": "categories",
"message": "categories exceeds 10 category limit"
},
{
"field": "html_content",
"message": "html_content exceeds the 1MB limit"
},
{
"field": "plain_content",
"message": "plain_content exceeds the 1MB limit"
},
{
"field": "sender_id",
"message": "sender_id does not exist"
},
{
"field": "sender_id",
"message": "sender_id is not a verified sender identity"
},
{
"field": "list_ids",
"message": "list_ids do not all exist"
},
{
"field": "segment_ids",
"message": "segment_ids do not all exist"
},
{
"field": "ip_pool",
"message": "The ip pool you provided is invalid"
},
{
"field": "suppression_group_id",
"message": "suppression_group_id does not exist"
},
{
"field": "unsubscribes",
"message": "Either suppression_group_id or custom_unsubscribe_url may be set/used, but not both. Please remove one before setting the other."
},
{
"field": null,
"message": "The JSON you have submitted cannot be parsed."
}
]
}
}
},
"schema": {
"type": "object"
}
}
},
"description": "\"title\": \"title can't be blank\"\n\"title\": \"title is too long (maximum is 100 characters)\"\n\"categories\": \"categories exceeds 10 category limit\"\n\"html_content\": \"html_content exceeds the 1MB limit\"\n\"plain_content\": \"plain_content exceeds the 1MB limit\"\n\"sender_id\": \"sender_id does not exist\"\n\"sender_id\": \"sender_id is not a verified sender identity\"\n\"list_ids\": \"list_ids do not all exist\"\n\"segment_ids\": \"segment_ids do not all exist\"\n\"ip_pool\": \"The ip pool you provided is invalid\"\n\"suppression_group_id\": \"suppression_group_id does not exist\"\n\"unsubscribes\": \"Either suppression_group_id or custom_unsubscribe_url may be set/used, but not both. Please remove one before setting the other.\"\n\"\": \"The JSON you have submitted cannot be parsed.\""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"403": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "You may only update a campaign when it is in draft mode."
}
]
}
}
},
"schema": {
"type": "object"
}
}
},
"description": "\"\": \"You may only update a campaign when it is in draft mode.\""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "not found"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"\": \"not found\""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update a Campaign",
"tags": [
"Campaigns API"
],
"x-stoplight": {
"id": "PATCH_campaigns-campaignid",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/campaigns/{campaign_id}/schedules": {
"delete": {
"description": "**This endpoint allows you to unschedule a campaign that has already been scheduled to be sent.**\n\nA successful unschedule will return a 204.\nIf the specified campaign is in the process of being sent, the only option is to cancel (a different method).",
"operationId": "DELETE_campaigns-campaign_id-schedules",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"nullable": true
}
}
},
"description": ""
},
"403": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "This campaign is already In Progress."
},
{
"field": null,
"message": "This campaign is already Sent."
},
{
"field": null,
"message": "This campaign is already Paused."
},
{
"field": null,
"message": "This campaign is already Canceled."
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"\": \"This campaign is already In Progress.\"\n\"\": \"This campaign is already Sent.\"\n\"\": \"This campaign is already Paused.\"\n\"\": \"This campaign is already Canceled.\""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "not found"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"\": \"not found\""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Unschedule a Scheduled Campaign",
"tags": [
"Campaigns API"
],
"x-stoplight": {
"id": "DELETE_campaigns-campaignid-schedules",
"mock": {
"dynamic": false
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve the date and time that a campaign has been scheduled to be sent.**",
"operationId": "GET_campaigns-campaign_id-schedules",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"send_at": 1490778528
}
}
},
"schema": {
"properties": {
"send_at": {
"format": "int64",
"type": "integer"
}
},
"required": [
"send_at"
],
"title": "View Scheduled Time of a Campaign response",
"type": "object"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "not found"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"\": \"not found\""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "View Scheduled Time of a Campaign",
"tags": [
"Campaigns API"
],
"x-stoplight": {
"id": "GET_campaigns-campaignid-schedules",
"mock": {
"dynamic": false
},
"public": true
}
},
"parameters": [
{
"in": "path",
"name": "campaign_id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"patch": {
"description": "**This endpoint allows to you change the scheduled time and date for a campaign to be sent.**",
"operationId": "PATCH_campaigns-campaign_id-schedules",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"send_at": 1489451436
},
"properties": {
"send_at": {
"format": "int64",
"type": "integer"
}
},
"required": [
"send_at"
],
"title": "Update a Scheduled Campaign request",
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"properties": {
"id": {
"description": "The campaign ID",
"type": "integer"
},
"send_at": {
"description": "The unix timestamp to send the campaign.",
"type": "integer"
},
"status": {
"description": "The status of the schedule.",
"type": "string"
}
},
"required": [
"id",
"send_at",
"status"
],
"title": "Update a Scheduled Campaign response",
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": "send_at",
"message": "Please choose a future time for sending your campaign."
},
{
"field": null,
"message": "The JSON you have submitted cannot be parsed."
},
{
"field": null,
"message": "You do not have enough credits to send this campaign. Upgrade your plan to send https://app.sendgrid.com/settings/billing"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"\": \"The JSON you have submitted cannot be parsed.\"\n\"send_at\": \"Please choose a future time for sending your campaign.\"\n\"\":\"You do not have enough credits to send this campaign. Upgrade your plan to send more: https://app.sendgrid.com/settings/billing\""
},
"403": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": "send_at",
"message": "You cannot update the send_at value of non-scheduled campaign."
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"send_at\": \"You cannot update the send_at value of non-scheduled campaign.\""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "not found"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"\": \"not found\""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update a Scheduled Campaign",
"tags": [
"Campaigns API"
],
"x-stoplight": {
"id": "PATCH_campaigns-campaignid-schedules",
"mock": {
"dynamic": false
},
"public": true
}
},
"post": {
"description": "**This endpoint allows you to schedule a specific date and time for your campaign to be sent.**\n\nIf you have the flexibility, it's better to schedule mail for off-peak times. Most emails are scheduled and sent at the top of the hour or half hour. Scheduling email to avoid those times (for example, scheduling at 10:53) can result in lower deferral rates because it won't be going through our servers at the same times as everyone else's mail.",
"operationId": "POST_campaigns-campaign_id-schedules",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"send_at": 1489771528
},
"properties": {
"send_at": {
"description": "The unix timestamp for the date and time you would like your campaign to be sent out.",
"type": "integer"
}
},
"required": [
"send_at"
],
"title": "Schedule a Campaign request",
"type": "object"
}
}
}
},
"responses": {
"201": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"id": 1234,
"send_at": 1489771528,
"status": "Scheduled"
}
}
},
"schema": {
"properties": {
"id": {
"description": "The campaign ID.",
"type": "integer"
},
"send_at": {
"description": "The date time you scheduled your campaign to be sent.",
"type": "integer"
},
"status": {
"description": "The status of your campaign.",
"enum": [
"Scheduled"
],
"type": "string"
}
},
"required": [
"id",
"send_at",
"status"
],
"title": "Schedule a Campaign response",
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": "subject",
"message": "subject can't be blank"
},
{
"field": "sender_id",
"message": "sender_id can't be blank"
},
{
"field": "plain_content",
"message": "plain_content can't be blank, please provide plain text or html content"
},
{
"field": "list_id",
"message": "You must select at least 1 segment or 1 list to send to."
},
{
"field": "unsubscribe_tag",
"message": "An [unsubscribe] tag in both your html and plain content is required to send a campaign."
},
{
"field": "suppression_group_id",
"message": "Either a suppression_group_id or custom_unsubscribe_url is required to send a campaign."
},
{
"field": null,
"message": "You do not have enough credits to send this campaign. Upgrade your plan to send more: https://app.sendgrid.com/settings/billing"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"subject\": \"subject can't be blank\"\n\"sender_id\": \"sender_id can't be blank\"\n\"plain_content\": \"plain_content can't be blank, please provide plain text or html content\"\n\"list_ids\": \"You must select at least 1 segment or 1 list to send to.\"\n\"send_at\": \"Please choose a future time for sending your campaign.\"\n\"unsubscribe_tag\": \"An [unsubscribe] tag in both your html and plain content is required to send a campaign.\"\n\"suppression_group_id\": \"Either a suppression_group_id or custom_unsubscribe_url is required to send a campaign.\"\n\"\": \"The JSON you have submitted cannot be parsed.\"\n\"\":\"You do not have enough credits to send this campaign. Upgrade your plan to send more: https://app.sendgrid.com/settings/billing\""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"403": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "You cannot POST to a campaign that has already sent or scheduled. However you can update a scheduled campaign with a PATCH."
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"\": \"You cannot POST to a campaign that has already sent or scheduled. However you can update a scheduled campaign with a PATCH.\""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "not found"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"\": \"not found\""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Schedule a Campaign",
"tags": [
"Campaigns API"
],
"x-stoplight": {
"id": "POST_campaigns-campaignid-schedules",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/campaigns/{campaign_id}/schedules/now": {
"parameters": [
{
"in": "path",
"name": "campaign_id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"post": {
"description": "**This endpoint allows you to immediately send an existing campaign.**\n\nNormally a POST request would have a body, but since this endpoint is telling us to send a resource that is already created, a request body is not needed.",
"operationId": "POST_campaigns-campaign_id-schedules-now",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"201": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"id": 1234,
"status": "Scheduled"
}
}
},
"schema": {
"properties": {
"id": {
"format": "int64",
"type": "integer"
},
"status": {
"type": "string"
}
},
"required": [
"id",
"status"
],
"title": "Send a Campaign response",
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": "subject",
"message": "subject can't be blank"
},
{
"field": "sender_id",
"message": "sender_id can't be blank"
},
{
"field": "plain_content",
"message": "plain_content can't be blank, please provide plain text or html content"
},
{
"field": "list_id",
"message": "You must select at least 1 segment or 1 list to send to."
},
{
"field": "unsubscribe_tag",
"message": "An [unsubscribe] tag in both your html and plain content is required to send a campaign."
},
{
"field": "suppression_group_id",
"message": "Either a suppression_group_id or custom_unsubscribe_url is required to send a campaign."
},
{
"field": null,
"message": "You do not have enough credits to send this campaign. Upgrade your plan to send more: https://app.sendgrid.com/settings/billing"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"subject\": \"subject can't be blank\"\n\"sender_id\": \"sender_id can't be blank\"\n\"plain_content\": \"plain_content can't be blank, please provide plain text or html content\"\n\"list_ids\": \"You must select at least 1 segment or 1 list to send to.\"\n\"unsubscribe_tag\": \"An [unsubscribe] tag in both your html and plain content is required to send a campaign.\"\n\"suppression_group_id\": \"Either a suppression_group_id or custom_unsubscribe_url is required to send a campaign.\"\n\"\": \"You do not have enough credits to send this campaign. Upgrade your plan to send more: https://app.sendgrid.com/settings/billing\""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"403": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "You may only send a campaign when it is in draft mode."
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"\": \"You may only send a campaign when it is in draft mode.\""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "not found"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"\": \"not found\""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Send a Campaign",
"tags": [
"Campaigns API"
],
"x-stoplight": {
"id": "POST_campaigns-campaignid-schedules-now",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/campaigns/{campaign_id}/schedules/test": {
"parameters": [
{
"in": "path",
"name": "campaign_id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"post": {
"description": "**This endpoint allows you to send a test campaign.**\n\nTo send to multiple addresses, use an array for the JSON \"to\" value [\"one@address\",\"two@address\"]",
"operationId": "POST_campaigns-campaign_id-schedules-test",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"to": "your.email@example.com"
},
"properties": {
"to": {
"description": "The email address that should receive the test campaign.",
"format": "email",
"type": "string"
}
},
"required": [
"to"
],
"type": "object"
}
}
}
},
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"properties": {
"to": {
"type": "string"
}
},
"required": [
"to"
],
"title": "Send a Test Campaign request",
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": "send_at",
"message": "Please choose a future time for sending your campaign."
},
{
"field": null,
"message": "The JSON you have submitted cannot be parsed."
},
{
"field": null,
"message": "You do not have enough credits to send this campaign. Upgrade your plan to send more: https://app.sendgrid.com/settings/billing"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"\": \"The JSON you have submitted cannot be parsed.\"\n\"to\": \"Please provide an email address to which the test should be sent.\"\n\"to\": \"You can only send tests to 10 addresses at a time.\"\n\"subject\": \"Please add a subject to your campaign before sending a test.\"\n\"plain_content\": \"Plain content and html content can't both be blank. Please set one of these values before sending a test.\"\n\"sender_id\": \"Please assign a sender identity to your campaign before sending a test.\""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "not found"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"\": \"not found\""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Send a Test Campaign",
"tags": [
"Campaigns API"
],
"x-stoplight": {
"id": "POST_campaigns-campaignid-schedules-test",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/categories": {
"get": {
"description": "**This endpoint allows you to retrieve a list of all of your categories.**",
"operationId": "GET_categories",
"parameters": [
{
"description": "The number of categories to display per page.",
"in": "query",
"name": "limit",
"schema": {
"default": 50,
"type": "integer"
}
},
{
"description": "Allows you to perform a prefix search on this particular category.",
"in": "query",
"name": "category",
"schema": {
"type": "string"
}
},
{
"description": "The point in the list that you would like to begin displaying results.",
"in": "query",
"name": "offset",
"schema": {
"default": 0,
"type": "integer"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"category": "category 1"
},
{
"category": "category 2"
}
]
}
},
"schema": {
"items": {
"properties": {
"category": {
"description": "A category used to group emails by broad topic.",
"type": "string"
}
},
"required": [
"category"
],
"type": "object"
},
"type": "array"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": "sort_by",
"message": "invalid sort value"
}
]
}
}
},
"schema": {
"properties": {
"errors": {
"description": "The error returned.",
"items": {
"properties": {
"field": {
"type": "string"
},
"message": {
"description": "A message explaining why your categories could not be retrieved.",
"type": "string"
}
},
"required": [
"field",
"message"
],
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve all categories",
"tags": [
"Categories"
],
"x-stoplight": {
"id": "GET_categories",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/categories/stats": {
"get": {
"description": "**This endpoint allows you to retrieve all of your email statistics for each of your categories.**\n\nIf you do not define any query parameters, this endpoint will return a sum for each category in groups of 10.",
"operationId": "GET_categories-stats",
"parameters": [
{
"description": "The starting date of the statistics to retrieve. Must follow format YYYY-MM-DD",
"in": "query",
"name": "start_date",
"required": true,
"schema": {
"type": "string"
}
},
{
"description": "The end date of the statistics to retrieve. Defaults to today. Must follow format YYYY-MM-DD.",
"in": "query",
"name": "end_date",
"required": false,
"schema": {
"type": "string"
}
},
{
"description": "The individual categories that you want to retrieve statistics for. You may include up to 10 different categories.",
"in": "query",
"name": "categories",
"required": true,
"schema": {
"type": "string"
}
},
{
"description": "The number of results to include.",
"in": "query",
"name": "limit",
"required": false,
"schema": {
"default": 500,
"maximum": 500,
"type": "integer"
}
},
{
"description": "The number of results to skip.",
"in": "query",
"name": "offset",
"required": false,
"schema": {
"type": "integer"
}
},
{
"description": "How to group the statistics. Must be either \"day\", \"week\", or \"month\".",
"in": "query",
"name": "aggregated_by",
"required": false,
"schema": {
"enum": [
"day",
"week",
"month"
],
"type": "string"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"date": "2015-10-01",
"stats": [
{
"metrics": {
"blocks": 0,
"bounce_drops": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"invalid_emails": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_report_drops": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0,
"unsubscribe_drops": 0,
"unsubscribes": 0
},
"name": "docs",
"type": "category"
},
{
"metrics": {
"blocks": 0,
"bounce_drops": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"invalid_emails": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_report_drops": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0,
"unsubscribe_drops": 0,
"unsubscribes": 0
},
"name": "mattscategory",
"type": "category"
}
]
},
{
"date": "2015-11-01",
"stats": [
{
"metrics": {
"blocks": 0,
"bounce_drops": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"invalid_emails": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_report_drops": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0,
"unsubscribe_drops": 0,
"unsubscribes": 0
},
"name": "docs",
"type": "category"
},
{
"metrics": {
"blocks": 0,
"bounce_drops": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"invalid_emails": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_report_drops": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0,
"unsubscribe_drops": 0,
"unsubscribes": 0
},
"name": "mattscategory",
"type": "category"
}
]
}
]
}
},
"schema": {
"items": {
"$ref": "#/components/schemas/category_stats"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve Email Statistics for Categories",
"tags": [
"Categories"
],
"x-stoplight": {
"id": "GET_categories-stats",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/categories/stats/sums": {
"get": {
"description": "**This endpoint allows you to retrieve the total sum of each email statistic for every category over the given date range.**\n\nIf you do not define any query parameters, this endpoint will return a sum for each category in groups of 10.",
"operationId": "GET_categories-stats-sums",
"parameters": [
{
"description": "The metric that you want to sort by. Must be a single metric.",
"in": "query",
"name": "sort_by_metric",
"required": false,
"schema": {
"default": "delivered",
"type": "string"
}
},
{
"description": "The direction you want to sort.",
"in": "query",
"name": "sort_by_direction",
"required": false,
"schema": {
"default": "desc",
"enum": [
"desc",
"asc"
],
"type": "string"
}
},
{
"description": "The starting date of the statistics to retrieve. Must follow format YYYY-MM-DD.",
"in": "query",
"name": "start_date",
"required": true,
"schema": {
"type": "string"
}
},
{
"description": "The end date of the statistics to retrieve. Defaults to today. Must follow format YYYY-MM-DD.",
"in": "query",
"name": "end_date",
"required": false,
"schema": {
"type": "string"
}
},
{
"description": "Limits the number of results returned.",
"in": "query",
"name": "limit",
"required": false,
"schema": {
"default": 5,
"type": "integer"
}
},
{
"description": "The point in the list to begin retrieving results.",
"in": "query",
"name": "offset",
"required": false,
"schema": {
"default": 0,
"type": "integer"
}
},
{
"description": "How to group the statistics. Must be either \"day\", \"week\", or \"month\".",
"in": "query",
"name": "aggregated_by",
"required": false,
"schema": {
"enum": [
"day",
"week",
"month"
],
"type": "string"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"date": "2015-01-01",
"stats": [
{
"metrics": {
"blocks": 0,
"bounce_drops": 0,
"bounces": 0,
"clicks": 20,
"deferred": 0,
"delivered": 20,
"invalid_emails": 0,
"opens": 20,
"processed": 0,
"requests": 20,
"spam_report_drops": 0,
"spam_reports": 0,
"unique_clicks": 20,
"unique_opens": 20,
"unsubscribe_drops": 0,
"unsubscribes": 20
},
"name": "cat1",
"type": "category"
},
{
"metrics": {
"blocks": 1,
"bounce_drops": 0,
"bounces": 0,
"clicks": 19,
"deferred": 0,
"delivered": 19,
"invalid_emails": 0,
"opens": 19,
"processed": 0,
"requests": 20,
"spam_report_drops": 0,
"spam_reports": 0,
"unique_clicks": 19,
"unique_opens": 19,
"unsubscribe_drops": 0,
"unsubscribes": 19
},
"name": "cat2",
"type": "category"
},
{
"metrics": {
"blocks": 0,
"bounce_drops": 0,
"bounces": 0,
"clicks": 5,
"deferred": 0,
"delivered": 5,
"invalid_emails": 0,
"opens": 5,
"processed": 0,
"requests": 5,
"spam_report_drops": 0,
"spam_reports": 0,
"unique_clicks": 5,
"unique_opens": 5,
"unsubscribe_drops": 0,
"unsubscribes": 5
},
"name": "cat3",
"type": "category"
},
{
"metrics": {
"blocks": 0,
"bounce_drops": 0,
"bounces": 0,
"clicks": 6,
"deferred": 0,
"delivered": 5,
"invalid_emails": 0,
"opens": 6,
"processed": 0,
"requests": 5,
"spam_report_drops": 0,
"spam_reports": 0,
"unique_clicks": 5,
"unique_opens": 5,
"unsubscribe_drops": 0,
"unsubscribes": 6
},
"name": "cat4",
"type": "category"
},
{
"metrics": {
"blocks": 10,
"bounce_drops": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"invalid_emails": 0,
"opens": 0,
"processed": 0,
"requests": 10,
"spam_report_drops": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0,
"unsubscribe_drops": 0,
"unsubscribes": 0
},
"name": "cat5",
"type": "category"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/category_stats"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve sums of email stats for each category [Needs: Stats object defined, has category ID?]",
"tags": [
"Categories"
],
"x-stoplight": {
"id": "GET_categories-stats-sums",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/clients/stats": {
"get": {
"description": "**This endpoint allows you to retrieve your email statistics segmented by client type.**\n\n**We only store up to 7 days of email activity in our database.** By default, 500 items will be returned per request via the Advanced Stats API endpoints.\n\nAdvanced Stats provide a more in-depth view of your email statistics and the actions taken by your recipients. You can segment these statistics by geographic location, device type, client type, browser, and mailbox provider. For more information about statistics, please see our [Statistics Overview](https://sendgrid.com/docs/ui/analytics-and-reporting/stats-overview/).",
"operationId": "GET_clients-stats",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
},
{
"$ref": "#/components/parameters/trait_statsAdvancedStatsBaseQueryStrings_start_date"
},
{
"$ref": "#/components/parameters/trait_statsAdvancedStatsBaseQueryStrings_end_date"
},
{
"$ref": "#/components/parameters/trait_statsAdvancedStatsBaseQueryStrings_aggregated_by"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"date": "2014-10-01",
"stats": [
{
"metrics": {
"opens": 1,
"unique_opens": 1
},
"name": "Gmail",
"type": "client"
}
]
},
{
"date": "2014-10-02",
"stats": [
{
"metrics": {
"opens": 0,
"unique_opens": 0
},
"name": "Gmail",
"type": "client"
}
]
}
]
}
},
"schema": {
"items": {
"properties": {
"date": {
"description": "The date that the statistics were gathered.",
"type": "string"
},
"stats": {
"description": "The list of statistics.",
"items": {
"properties": {
"metrics": {
"$ref": "#/components/schemas/advanced_stats_opens"
},
"name": {
"description": "The name of the specific segmentation.",
"type": "string"
},
"type": {
"description": "The type of segmentation.",
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve email statistics by client type.",
"tags": [
"Stats"
],
"x-stoplight": {
"id": "GET_clients-stats",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/clients/{client_type}/stats": {
"get": {
"description": "**This endpoint allows you to retrieve your email statistics segmented by a specific client type.**\n\n**We only store up to 7 days of email activity in our database.** By default, 500 items will be returned per request via the Advanced Stats API endpoints.\n\n## Available Client Types\n- phone\n- tablet\n- webmail\n- desktop\n\nAdvanced Stats provide a more in-depth view of your email statistics and the actions taken by your recipients. You can segment these statistics by geographic location, device type, client type, browser, and mailbox provider. For more information about statistics, please see our [Statistics Overview](https://sendgrid.com/docs/ui/analytics-and-reporting/stats-overview/).",
"operationId": "GET_clients-client_type-stats",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
},
{
"$ref": "#/components/parameters/trait_statsAdvancedStatsBaseQueryStrings_start_date"
},
{
"$ref": "#/components/parameters/trait_statsAdvancedStatsBaseQueryStrings_end_date"
},
{
"$ref": "#/components/parameters/trait_statsAdvancedStatsBaseQueryStrings_aggregated_by"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"date": "2014-10-01",
"stats": [
{
"metrics": {
"opens": 1,
"unique_opens": 1
},
"name": "Gmail",
"type": "client"
}
]
},
{
"date": "2014-10-02",
"stats": [
{
"metrics": {
"opens": 0,
"unique_opens": 0
},
"name": "Gmail",
"type": "client"
}
]
}
]
}
},
"schema": {
"items": {
"properties": {
"date": {
"description": "The date that the statistics were gathered.",
"type": "string"
},
"stats": {
"description": "The list of statistics.",
"items": {
"properties": {
"metrics": {
"$ref": "#/components/schemas/advanced_stats_opens"
},
"name": {
"description": "The name of the specific segmentation.",
"type": "string"
},
"type": {
"description": "The type of segmentation.",
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve stats by a specific client type.",
"tags": [
"Stats"
],
"x-stoplight": {
"id": "GET_clients-clienttype-stats",
"mock": {
"dynamic": false
},
"public": true
}
},
"parameters": [
{
"description": "Specifies the type of client to retrieve stats for. Must be either \"phone\", \"tablet\", \"webmail\", or \"desktop\".",
"in": "path",
"name": "client_type",
"required": true,
"schema": {
"enum": [
"phone",
"tablet",
"webmail",
"desktop"
],
"type": "string"
}
}
]
},
"/contactdb/custom_fields": {
"get": {
"description": "**This endpoint allows you to retrieve all custom fields.**",
"operationId": "GET_contactdb-custom_fields",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"custom_fields": [
{
"id": 6234,
"name": "age",
"type": "number"
},
{
"id": 6233,
"name": "country",
"type": "text"
},
{
"id": 6235,
"name": "favorite_color",
"type": "text"
},
{
"id": 6239,
"name": "fname",
"type": "text"
},
{
"id": 6240,
"name": "lname",
"type": "text"
},
{
"id": 49439,
"name": "pet",
"type": "text"
}
]
}
}
},
"schema": {
"properties": {
"custom_fields": {
"items": {
"$ref": "#/components/schemas/contactdb_custom_field_with_id"
},
"type": "array"
}
},
"required": [
"custom_fields"
],
"title": "List All Custom Fields response",
"type": "object"
}
}
},
"description": ""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve all custom fields",
"tags": [
"Contacts API - Custom Fields"
],
"x-stoplight": {
"id": "GET_contactdb-customfields",
"mock": {
"dynamic": false
},
"public": true
}
},
"post": {
"description": "**This endpoint allows you to create a custom field.**\n\n**You can create up to 120 custom fields.**",
"operationId": "POST_contactdb-custom_fields",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"name": "pet",
"type": "text"
},
"properties": {
"name": {
"type": "string"
},
"type": {
"type": "string"
}
},
"type": "object"
}
}
}
},
"responses": {
"201": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"id": 1,
"name": "pet",
"type": "text"
}
}
},
"schema": {
"$ref": "#/components/schemas/contactdb_custom_field_with_id"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "Returned if request body is invalid JSON"
},
{
"field": "type",
"message": "Returned if custom field type is invalid or not provided"
},
{
"field": "name",
"message": "Returned if custom field name is not provided"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"\" : \"Returned if request body is invalid JSON\"\n\"type\" : \"Returned if custom field type is invalid or not provided\"\n\"name\" : \"Returned if custom field name is not provided\""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Create a Custom Field",
"tags": [
"Contacts API - Custom Fields"
],
"x-stoplight": {
"id": "POST_contactdb-customfields",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/contactdb/custom_fields/{custom_field_id}": {
"delete": {
"description": "**This endpoint allows you to delete a custom field by ID.**",
"operationId": "DELETE_contactdb-custom_fields-custom_field_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"202": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"message": "Custom Field delete is processing."
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"message": "Custom field in use by one or more segment conditions"
},
{
"message": "Custom field ID does not exist"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"id\" : \"Returned if custom_field_id is not valid\""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"message": "Custom field ID does not exist"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"custom_field_id\" : \"Returned if custom_field_id does not exist\""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete a Custom Field",
"tags": [
"Contacts API - Custom Fields"
],
"x-stoplight": {
"id": "DELETE_contactdb-customfields-customfieldid",
"mock": {
"dynamic": false
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve a custom field by ID.**",
"operationId": "GET_contactdb-custom_fields-custom_field_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"id": 1,
"name": "pet",
"type": "text"
}
}
},
"schema": {
"$ref": "#/components/schemas/contactdb_custom_field_with_id"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"message": "invalid id"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"message": "Custom field ID does not exist"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"custom_field_id\" : \"Returned if custom_field_id does not exist\""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve a Custom Field",
"tags": [
"Contacts API - Custom Fields"
],
"x-stoplight": {
"id": "GET_contactdb-customfields-customfieldid",
"mock": {
"dynamic": false
},
"public": true
}
},
"parameters": [
{
"description": "The ID of the custom field that you want to retrieve.",
"in": "path",
"name": "custom_field_id",
"required": true,
"schema": {
"type": "integer"
}
}
]
},
"/contactdb/lists": {
"delete": {
"description": "**This endpoint allows you to delete multiple recipient lists.**",
"operationId": "DELETE_contactdb-lists",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": [
1,
2,
3,
4
],
"items": {
"type": "integer"
},
"type": "array"
}
}
}
},
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"nullable": true
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "list id was invalid"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"id\" : \"Returned if all list ids are not valid\""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete Multiple lists",
"tags": [
"Contacts API - Lists"
],
"x-stoplight": {
"id": "DELETE_contactdb-lists",
"mock": {
"dynamic": false
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve all of your recipient lists. If you don't have any lists, an empty array will be returned.**",
"operationId": "GET_contactdb-lists",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"lists": [
{
"id": 1,
"name": "the jones",
"recipient_count": 1
}
]
}
}
},
"schema": {
"properties": {
"lists": {
"items": {
"$ref": "#/components/schemas/contactdb_list"
},
"type": "array"
}
},
"required": [
"lists"
],
"title": "List All Lists response",
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve all lists",
"tags": [
"Contacts API - Lists"
],
"x-stoplight": {
"id": "GET_contactdb-lists",
"mock": {
"dynamic": false
},
"public": true
}
},
"post": {
"description": "**This endpoint allows you to create a list for your recipients.**",
"operationId": "POST_contactdb-lists",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"name": "your list name"
},
"properties": {
"name": {
"type": "string"
}
},
"required": [
"name"
],
"title": "Create a List request",
"type": "object"
}
}
}
},
"responses": {
"201": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"id": 1,
"name": "your list name",
"recipient_count": 0
}
}
},
"schema": {
"$ref": "#/components/schemas/contactdb_list"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "Returned if request body is invalid JSON"
},
{
"field": "name",
"message": "Returned if list name is not a string"
},
{
"field": "name",
"message": "Returned if list name is a duplicate of an existing list or segment"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"name\" : \"Returned if list name is a duplicate of an existing list or segment\"\n\"name\" : \"Returned if list name is not a string\"\n\"\" : \"Returned if request body is invalid JSON\""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Create a List",
"tags": [
"Contacts API - Lists"
],
"x-stoplight": {
"id": "POST_contactdb-lists",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/contactdb/lists/{list_id}": {
"delete": {
"description": "**This endpoint allows you to delete a specific recipient list with the given ID.**",
"operationId": "DELETE_contactdb-lists-list_id",
"parameters": [
{
"description": "Adds the ability to delete all contacts on the list in addition to deleting the list.",
"in": "query",
"name": "delete_contacts",
"schema": {
"enum": [
true,
false
],
"type": "boolean"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"$ref": "#/components/requestBodies/DELETE_contactdb-lists-list_idBody"
},
"responses": {
"202": {
"content": {
"application/json": {
"schema": {
"nullable": true
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": "delete_contacts",
"message": "delete_contacts not a bool"
},
{
"field": "list_id",
"message": "Returned if list_id is not valid"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"list_id\" : \"Returned if list_id is not valid\"\n\"delete_contacts\" : \"Returned if delete_contacts is not valid\""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"message": "List not found: 5"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"list_id\" : \"Returned if list_id does not exist\""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete a List",
"tags": [
"Contacts API - Lists"
],
"x-stoplight": {
"id": "DELETE_contactdb-lists-listid",
"mock": {
"dynamic": false
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve a single recipient list.**",
"operationId": "GET_contactdb-lists-list_id",
"parameters": [
{
"description": "The ID of the list to retrieve.",
"in": "query",
"name": "list_id",
"schema": {
"type": "integer"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"id": 1,
"name": "listname",
"recipient_count": 0
}
}
},
"schema": {
"$ref": "#/components/schemas/contactdb_list"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"message": "invalid id"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"list_id\" : \"Returned if list_id is not valid\""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "List ID does not exist"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"list_id\" : \"Returned if list_id does not exist\""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve a single list",
"tags": [
"Contacts API - Lists"
],
"x-stoplight": {
"id": "GET_contactdb-lists-listid",
"mock": {
"dynamic": false
},
"public": true
}
},
"parameters": [
{
"in": "path",
"name": "list_id",
"required": true,
"schema": {
"type": "string"
}
}
],
"patch": {
"description": "**This endpoint allows you to update the name of one of your recipient lists.**",
"operationId": "PATCH_contactdb-lists-list_id",
"parameters": [
{
"description": "The ID of the list you are updating.",
"in": "query",
"name": "list_id",
"required": true,
"schema": {
"type": "integer"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"name": "newlistname"
},
"properties": {
"name": {
"description": "The new name for your list. ",
"type": "string"
}
},
"required": [
"name"
],
"title": "Update a List request",
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"id": 1234,
"name": "2016 iPhone Users",
"recipient_count": 0
}
}
},
"schema": {
"properties": {
"id": {
"description": "The ID of the list",
"type": "integer"
},
"name": {
"description": "The new name for the list",
"type": "string"
},
"recipient_count": {
"description": "The number of recipients on the list",
"type": "integer"
}
},
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"message": "invalid id"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"name\" : \"Returned if list name is a duplicate of existing list or segment\"\n\"name\" : \"Returned if list name is invalid or not provided\"\n\"list_id\" : \"Returned if list_id is not valid\"\n\"\" : \"Returned if request body is invalid JSON\""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"message": "List ID does not exist"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"list_id\" : \"Returned if list_id does not exist\""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update a List",
"tags": [
"Contacts API - Lists"
],
"x-stoplight": {
"id": "PATCH_contactdb-lists-listid",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/contactdb/lists/{list_id}/recipients": {
"get": {
"description": "**This endpoint allows you to retrieve all recipients on the list with the given ID.**",
"operationId": "GET_contactdb-lists-list_id-recipients",
"parameters": [
{
"description": "Page index of first recipient to return (must be a positive integer)",
"in": "query",
"name": "page",
"required": false,
"schema": {
"type": "integer"
}
},
{
"description": "Number of recipients to return at a time (must be a positive integer between 1 and 1000)",
"in": "query",
"name": "page_size",
"required": false,
"schema": {
"type": "integer"
}
},
{
"description": "The ID of the list whose recipients you are requesting.",
"in": "query",
"name": "list_id",
"required": true,
"schema": {
"type": "integer"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"recipients": [
{
"created_at": 1433348344,
"custom_fields": [
{
"id": 6234,
"name": "age",
"type": "number",
"value": null
},
{
"id": 6233,
"name": "country",
"type": "text",
"value": null
},
{
"id": 6235,
"name": "fname",
"type": "text",
"value": "Example"
},
{
"id": 6239,
"name": "lname",
"type": "text",
"value": "User"
},
{
"id": 6240,
"name": "lname",
"type": "text",
"value": null
}
],
"email": "example@example.com",
"first_name": "Example",
"id": "ZGVWfyZWsuYmFpbmVzQHNlbmRmCmLkLmNv==",
"last_clicked": 1438616117,
"last_emailed": 1438613272,
"last_name": "User",
"last_opened": 1438616109,
"updated_at": 1438616119
}
]
}
}
},
"schema": {
"properties": {
"recipients": {
"items": {
"$ref": "#/components/schemas/contactdb_recipient"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": "list_id",
"message": "Returned if list_id is not a valid integer"
},
{
"field": "page",
"message": "Returned if page is not a valid integer"
},
{
"field": "page",
"message": "Returned if page is less than 1"
},
{
"field": "page_size",
"message": "Returned if page_size is not a valid integer"
},
{
"field": "page_size",
"message": "Returned if page_size is less than 1 or greater than 1000"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"list_id\" : \"Returned if list_id is not a valid integer\"\n\"page\" : \"Returned if page is not a valid integer\"\n\"page\" : \"Returned if page is less than 1\"\n\"page_size\" : \"Returned if page_size is not a valid integer\"\n\"page_size\" : \"Returned if page_size is less than 1 or greater than 1000\""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": "list_id",
"message": "Returned if list_id is invalid"
}
]
}
}
},
"schema": {
"type": "object"
}
}
},
"description": "\"list_id\" : \"Returned if list_id does not exist\""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve all recipients on a List",
"tags": [
"Contacts API - Lists"
],
"x-stoplight": {
"id": "GET_contactdb-lists-listid-recipients",
"mock": {
"dynamic": false
},
"public": true
}
},
"parameters": [
{
"description": "The id of the list of recipients you want to retrieve.",
"in": "path",
"name": "list_id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"post": {
"description": "**This endpoint allows you to add multiple recipients to a list.**\n\nAdds existing recipients to a list, passing in the recipient IDs to add. Recipient IDs should be passed exactly as they are returned from recipient endpoints.",
"operationId": "POST_contactdb-lists-list_id-recipients",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": [
1,
2
],
"items": {
"type": "integer"
},
"type": "array"
}
}
}
},
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"nullable": true
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": "list_id",
"message": "list_id is invalid"
},
{
"field": "recipient_id",
"message": "no valid recipients were provided"
},
{
"field": null,
"message": "no recipients were added"
},
{
"field": null,
"message": "request body is invalid JSON"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"list_id\" : \"Returned if list_id is not a valid integer\"\n\"\" : \"Returned if no valid recipient ids were passed\"\n\"\" : \"Returned if no recipients were added\"\n\"\" : \"Returned if request body is invalid JSON\""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": "list_id",
"message": "list_id does not exist"
},
{
"field": "recipient_id",
"message": "recipient_id does not exist"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"list_id\": \"Returned if list_id does not exist\""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Add Multiple Recipients to a List",
"tags": [
"Contacts API - Lists"
],
"x-stoplight": {
"id": "POST_contactdb-lists-listid-recipients",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/contactdb/lists/{list_id}/recipients/{recipient_id}": {
"delete": {
"description": "**This endpoint allows you to delete a single recipient from a list.**",
"operationId": "DELETE_contactdb-lists-list_id-recipients-recipient_id",
"parameters": [
{
"description": "The ID of the list you are taking this recipient away from.",
"in": "query",
"name": "list_id",
"required": true,
"schema": {
"type": "integer"
}
},
{
"description": "The ID of the recipient to take off the list.",
"in": "query",
"name": "recipient_id",
"required": true,
"schema": {
"type": "integer"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"$ref": "#/components/requestBodies/DELETE_contactdb-lists-list_idBody"
},
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"nullable": true
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": "list_id",
"message": "Returned if list_id is invalid"
},
{
"field": "recipient_id",
"message": "no valid recipients were provided"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"list_id\" : \"Returned if list_id is not valid\"\n\"recipient_id\" : \"Returned if recipient_id is not valid\""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": "list_id",
"message": "Returned if list_id does not exist"
},
{
"field": "recipient_id",
"message": "Returned if recipient_id does not exist"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"list_id\" : \"Returned if list_id does not exist\"\n\"recipient_id\" : \"Returned if recipient_id does not exist\""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete a Single Recipient from a Single List",
"tags": [
"Contacts API - Lists"
],
"x-stoplight": {
"id": "DELETE_contactdb-lists-listid-recipients-recipientid",
"mock": {
"dynamic": false
},
"public": true
}
},
"parameters": [
{
"description": "The ID of the list that you want to add the recipient to.",
"in": "path",
"name": "list_id",
"required": true,
"schema": {
"type": "integer"
}
},
{
"description": "The ID of the recipient you are adding to the list.",
"in": "path",
"name": "recipient_id",
"required": true,
"schema": {
"type": "string"
}
}
],
"post": {
"description": "**This endpoint allows you to add a single recipient to a list.**",
"operationId": "POST_contactdb-lists-list_id-recipients-recipient_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"nullable": true
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": "list_id",
"message": "Returned if list_id is invalid"
},
{
"field": "recipient_id",
"message": "Returned if recipient_id is invalid"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"list_id\" : \"Returned if list_id is invalid\"\n\"recipient_id\" : \"Returned if recipient_id is invalid\""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": "list_id",
"message": "Returned if list_id does not exist"
},
{
"field": "recipient_id",
"message": "Returned if recipient_id does not exist"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"list_id\" : \"Returned if list_id does not exist\"\n\"recipient_id\" : \"Returned if recipient_id does not exist\""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Add a Single Recipient to a List",
"tags": [
"Contacts API - Lists"
],
"x-stoplight": {
"id": "POST_contactdb-lists-listid-recipients-recipientid",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/contactdb/recipients": {
"delete": {
"description": "**This endpoint allows you to deletes one or more recipients.**\n\nThe body of an API call to this endpoint must include an array of recipient IDs of the recipients you want to delete.",
"operationId": "DELETE_contactdb-recipients",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": [
"recipient_id1",
"recipient_id2"
],
"items": {
"type": "string"
},
"type": "array"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"message": "No recipient ids provided"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"\" : \"Returned if no recipients are deleted\"\n\"\" : \"Returned if all of the provided recipient ids are invalid\"\n\"\" : \"Returned if request body is not valid json\""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete Recipients",
"tags": [
"Contacts API - Recipients"
],
"x-stoplight": {
"id": "DELETE_contactdb-recipients",
"mock": {
"dynamic": false
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve all of your Marketing Campaigns recipients.**\n\nBatch deletion of a page makes it possible to receive an empty page of recipients before reaching the end of\nthe list of recipients. To avoid this issue; iterate over pages until a 404 is retrieved.",
"operationId": "GET_contactdb-recipients",
"parameters": [
{
"description": "Page index of first recipients to return (must be a positive integer)",
"in": "query",
"name": "page",
"schema": {
"type": "integer"
}
},
{
"description": "Number of recipients to return at a time (must be a positive integer between 1 and 1000)",
"in": "query",
"name": "page_size",
"schema": {
"type": "integer"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"properties": {
"recipients": {
"items": {
"type": "object"
},
"type": "array"
}
},
"required": [
"recipients"
],
"title": "List Recipients response",
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": "\"page\" : \"Returned if page is not a valid integer\"\n\"page\" : \"Returned if page is less than 1\"\n\"page_size\" : \"Returned if page_size is not a valid integer\"\n\"page_size\" : \"Returned if page_size is less than 1 or greater than 1000\""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve recipients",
"tags": [
"Contacts API - Recipients"
],
"x-stoplight": {
"id": "GET_contactdb-recipients",
"mock": {
"dynamic": false
},
"public": true
}
},
"patch": {
"description": "**This endpoint allows you to update one or more recipients.**\n\nThe body of an API call to this endpoint must include an array of one or more recipient objects.\n\nIt is of note that you can add custom field data as parameters on recipient objects. We have provided an example using some of the default custom fields SendGrid provides.",
"operationId": "PATCH_contactdb-recipients",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": [
{
"email": "jones@example.com",
"first_name": "Guy",
"last_name": "Jones"
}
],
"items": {
"properties": {
"email": {
"format": "email",
"type": "string"
},
"first_name": {
"description": "The first name of the recipient. This is one of the default custom fields.",
"type": "string"
},
"last_name": {
"description": "The last name of the recipient. This is one of the default custom fields.",
"type": "string"
}
},
"required": [
"email"
],
"type": "object"
},
"type": "array"
}
}
}
},
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/contactdb_recipient_response"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "Request body is not valid json"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"\" : \"Returned if request body is not valid json\""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update Recipient",
"tags": [
"Contacts API - Recipients"
],
"x-stoplight": {
"id": "PATCH_contactdb-recipients",
"mock": {
"dynamic": false
},
"public": true
}
},
"post": {
"description": "**This endpoint allows you to add a Marketing Campaigns recipient.**\n\nYou can add custom field data as a parameter on this endpoint. We have provided an example using some of the default custom fields SendGrid provides.\n\nThe rate limit is three requests every 2 seconds. You can upload 1000 contacts per request. So the maximum upload rate is 1500 recipients per second.",
"operationId": "POST_contactdb-recipients",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": [
{
"age": 25,
"email": "example@example.com",
"first_name": "",
"last_name": "User"
},
{
"age": 25,
"email": "example2@example.com",
"first_name": "Example",
"last_name": "User"
}
],
"items": {
"properties": {
"age": {
"type": "integer"
},
"email": {
"description": "The email address of the recipient.",
"format": "email",
"type": "string"
},
"first_name": {
"description": "The first name of the recipient.",
"type": "string"
},
"last_name": {
"description": "The last name of the recipient.",
"type": "string"
}
},
"required": [
"email"
],
"type": "object"
},
"type": "array"
}
}
}
},
"responses": {
"201": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"error_count": 1,
"error_indices": [
2
],
"errors": [
{
"error_indices": [
2
],
"message": "Invalid email."
}
],
"new_count": 2,
"persisted_recipients": [
"YUBh",
"bWlsbGVyQG1pbGxlci50ZXN0"
],
"updated_count": 0
}
}
},
"schema": {
"$ref": "#/components/schemas/contactdb_recipient_response"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "Request body is not valid json"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"\" : \"Returned if request body is not valid json\""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Add recipients",
"tags": [
"Contacts API - Recipients"
],
"x-stoplight": {
"id": "POST_contactdb-recipients",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/contactdb/recipients/billable_count": {
"get": {
"description": "**This endpoint allows you to retrieve the number of Marketing Campaigns recipients that you will be billed for.**\n\nYou are billed for marketing campaigns based on the highest number of recipients you have had in your account at one time. This endpoint will allow you to know the current billable count value.",
"operationId": "GET_contactdb-recipients-billable_count",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"recipient_count": 1234
}
}
},
"schema": {
"$ref": "#/components/schemas/contactdb_recipient_count"
}
}
},
"description": ""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve the count of billable recipients",
"tags": [
"Contacts API - Recipients"
],
"x-stoplight": {
"id": "GET_contactdb-recipients-billablecount",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/contactdb/recipients/count": {
"get": {
"description": "**This endpoint allows you to retrieve the total number of Marketing Campaigns recipients.**",
"operationId": "GET_contactdb-recipients-count",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"recipient_count": 1234
}
}
},
"schema": {
"$ref": "#/components/schemas/contactdb_recipient_count"
}
}
},
"description": ""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve a Count of Recipients",
"tags": [
"Contacts API - Recipients"
],
"x-stoplight": {
"id": "GET_contactdb-recipients-count",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/contactdb/recipients/search": {
"get": {
"description": "**This endpoint allows you to perform a search on all of your Marketing Campaigns recipients.**\n\nfield_name:\n\n* is a variable that is substituted for your actual custom field name from your recipient.\n* Text fields must be url-encoded. Date fields are searchable only by unix timestamp (e.g. 2/2/2015 becomes 1422835200)\n* If field_name is a 'reserved' date field, such as created_at or updated_at, the system will internally convert\nyour epoch time to a date range encompassing the entire day. For example, an epoch time of 1422835600 converts to\nMon, 02 Feb 2015 00:06:40 GMT, but internally the system will search from Mon, 02 Feb 2015 00:00:00 GMT through\nMon, 02 Feb 2015 23:59:59 GMT.",
"operationId": "GET_contactdb-recipients-search",
"parameters": [
{
"in": "query",
"name": "{field_name}",
"schema": {
"type": "string"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"recipients": [
{
"created_at": 1422313607,
"custom_fields": [
{
"id": 23,
"name": "pet",
"type": "text",
"value": "Fluffy"
}
],
"email": "jones@example.com",
"first_name": null,
"id": "YUBh",
"last_clicked": null,
"last_emailed": null,
"last_name": "Jones",
"last_opened": null,
"updated_at": 1422313790
}
]
}
}
},
"schema": {
"properties": {
"recipients": {
"items": {
"$ref": "#/components/schemas/contactdb_recipient"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"message": "The following parameters are not custom fields or reserved fields: [{field_name}]"
},
{
"message": "No search params are specified"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"\" : \"Returned if no search params are specified\"\n\"field\" : \"Returned if the provided field is invalid or does not exist\""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Search recipients",
"tags": [
"Contacts API - Recipients"
],
"x-stoplight": {
"id": "GET_contactdb-recipients-search",
"mock": {
"dynamic": false
},
"public": true
}
},
"post": {
"description": "\n Search using segment conditions without actually creating a segment.\n Body contains a JSON object with conditions
, a list of conditions as described below, and an optional list_id
, which is a valid list ID for a list to limit the search on.\n
\n\n\n Valid operators for create and update depend on the type of the field for which you are searching.\n
\n\n\n Dates:\n \n \"eq\", \"ne\", \"lt\" (before), \"gt\" (after)\n \n You may use MM/DD/YYYY for day granularity or an epoch for second granularity. \n \n \n \"empty\", \"not_empty\" \n \"is within\"\n \n \n \n \n Text: \"contains\", \"eq\" (is - matches the full field), \"ne\" (is not - matches any field where the entire field is not the condition value), \"empty\", \"not_empty\" \n Numbers: \"eq\", \"lt\", \"gt\", \"empty\", \"not_empty\" \n Email Clicks and Opens: \"eq\" (opened), \"ne\" (not opened) \n \n\n\n Field values must all be a string.\n
\n\n\n Search conditions using \"eq\" or \"ne\" for email clicks and opens should provide a \"field\" of either clicks.campaign_identifier
or opens.campaign_identifier
.\n The condition value should be a string containing the id of a completed campaign.\n
\n\n\n Search conditions list may contain multiple conditions, joined by an \"and\" or \"or\" in the \"and_or\" field.\n The first condition in the conditions list must have an empty \"and_or\", and subsequent conditions must all specify an \"and_or\".\n
",
"operationId": "POST_contactdb-recipients-search",
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"conditions": [
{
"and_or": "",
"field": "birthday",
"operator": "eq",
"value": "01/12/1985"
},
{
"and_or": "",
"field": "birthday",
"operator": "eq",
"value": "01/12/1985"
},
{
"and_or": "",
"field": "birthday",
"operator": "eq",
"value": "01/12/1985"
},
{
"and_or": "",
"field": "birthday",
"operator": "eq",
"value": "01/12/1985"
}
],
"list_id": -27497588
},
"properties": {
"conditions": {
"description": "The conditions by which this segment should be created.",
"items": {
"$ref": "#/components/schemas/contactdb_segments_conditions"
},
"type": "array"
},
"list_id": {
"format": "int32",
"type": "integer"
}
},
"required": [
"list_id",
"conditions"
],
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"recipient_count": 65190677,
"recipients": [
{
"created_at": -27901208,
"email": "ut magna quis ipsum",
"id": "fugiat ad adipisicing ullamco",
"last_emailed": 21626657
},
{
"created_at": 17466400,
"email": "sunt irure",
"first_name": "est",
"id": "et",
"last_clicked": -23135244,
"last_opened": -44593357
},
{
"created_at": -34495329,
"email": "reprehenderit incididunt velit Lorem esse",
"id": "esse Ut ad dolore",
"last_clicked": 10164083,
"last_opened": 34443062
},
{
"created_at": -37030673,
"email": "amet deserunt fugiat voluptate",
"id": "et exercitation commodo id laborum",
"last_clicked": -10497425
},
{
"created_at": 3658435,
"custom_fields": [
{
"id": -5765608,
"name": "proident pariatur",
"type": "dolore ut",
"value": "do in magna mollit"
},
{
"id": -31131201,
"name": "laborum mollit",
"type": "veniam",
"value": 84434696
}
],
"email": "labore veniam",
"first_name": "Ut cupidatat nulla deserunt adipisicing",
"id": "ad pariatur esse",
"last_clicked": -52862671,
"last_opened": -84227501,
"updated_at": -56455352
}
]
}
}
},
"schema": {
"properties": {
"recipient_count": {
"type": "integer"
},
"recipients": {
"items": {
"properties": {
"created_at": {
"type": "integer"
},
"custom_fields": {
"items": {
"properties": {
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"type": {
"type": "string"
},
"value": {
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
]
}
},
"type": "object"
},
"type": "array"
},
"email": {
"type": "string"
},
"first_name": {
"type": "string"
},
"id": {
"type": "string"
},
"last_clicked": {
"type": "integer"
},
"last_emailed": {
"type": "integer"
},
"last_opened": {
"type": "integer"
},
"updated_at": {
"type": "integer"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"400": {
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Search recipients",
"tags": [
"Contacts API - Recipients"
],
"x-stoplight": {
"id": "POST_contactdb-recipients-search",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/contactdb/recipients/{recipient_id}": {
"delete": {
"description": "**This endpoint allows you to delete a single recipient with the given ID from your contact database.**\n\n> Use this to permanently delete your recipients from all of your contact lists and all segments if required by applicable law.",
"operationId": "DELETE_contactdb-recipients-recipient_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "recipient not found"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"recipient_id\" : \"Returned if recipient_id is not valid\""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "recipient_id is not valid"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"recipient_id\" : \"Returned if record for recipient id does not exist\""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete a Recipient",
"tags": [
"Contacts API - Recipients"
],
"x-stoplight": {
"id": "DELETE_contactdb-recipients-recipientid",
"mock": {
"dynamic": false
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve a single recipient by ID from your contact database.**",
"operationId": "GET_contactdb-recipients-recipient_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/contactdb_recipient"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": "\"recipient_id\" : \"Returned if recipient_id is not valid\""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": "\"recipient_id\" : \"Returned if record for recipient id does not exist\""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve a single recipient",
"tags": [
"Contacts API - Recipients"
],
"x-stoplight": {
"id": "GET_contactdb-recipients-recipientid",
"mock": {
"dynamic": false
},
"public": true
}
},
"parameters": [
{
"description": "The ID of the recipient that you want to retrieve.",
"in": "path",
"name": "recipient_id",
"required": true,
"schema": {
"type": "string"
}
}
]
},
"/contactdb/recipients/{recipient_id}/lists": {
"get": {
"description": "**This endpoint allows you to retrieve the lists that a given recipient belongs to.**\n\nEach recipient can be on many lists. This endpoint gives you all of the lists that any one recipient has been added to.",
"operationId": "GET_contactdb-recipients-recipient_id-lists",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"lists": [
{
"id": 1234,
"name": "Example list",
"recipient_count": 42
}
]
}
}
},
"schema": {
"properties": {
"lists": {
"items": {
"$ref": "#/components/schemas/contactdb_list"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "recipient ID is invalid"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"recipient_id\" : \"Returned if recipient_id is not valid\""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "recipient id not found"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"recipient_id\" : \"Returned if record for the recipient id does not exist\""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve the lists that a recipient is on",
"tags": [
"Contacts API - Recipients"
],
"x-stoplight": {
"id": "GET_contactdb-recipients-recipientid-lists",
"mock": {
"dynamic": false
},
"public": true
}
},
"parameters": [
{
"description": "The ID of the recipient for whom you are retrieving lists.",
"in": "path",
"name": "recipient_id",
"required": true,
"schema": {
"type": "string"
}
}
]
},
"/contactdb/reserved_fields": {
"get": {
"description": "**This endpoint allows you to list all fields that are reserved and can't be used for custom field names.**",
"operationId": "GET_contactdb-reserved_fields",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"reserved_fields": [
{
"name": "first_name",
"type": "text"
},
{
"name": "last_name",
"type": "text"
},
{
"name": "email",
"type": "text"
},
{
"name": "created_at",
"type": "date"
},
{
"name": "updated_at",
"type": "date"
},
{
"name": "last_emailed",
"type": "date"
},
{
"name": "last_clicked",
"type": "date"
},
{
"name": "last_opened",
"type": "date"
},
{
"name": "lists",
"type": "set"
},
{
"name": "campaigns",
"type": "set"
},
{
"name": "my_custom_field",
"type": "text"
}
]
}
}
},
"schema": {
"properties": {
"reserved_fields": {
"items": {
"properties": {
"name": {
"type": "string"
},
"type": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve reserved fields",
"tags": [
"Contacts API - Custom Fields"
],
"x-stoplight": {
"id": "GET_contactdb-reservedfields",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/contactdb/segments": {
"get": {
"description": "**This endpoint allows you to retrieve all of your segments.**",
"operationId": "GET_contactdb-segments",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"segments": [
{
"conditions": [
{
"field": "age",
"operator": "lt",
"value": "25"
}
],
"id": 1234,
"name": "Age segments < 25",
"recipient_count": 8
},
{
"conditions": [
{
"field": "email",
"operator": "contains",
"value": "@gmail.com"
}
],
"id": 2345,
"name": "email address - gmail",
"recipient_count": 0
}
]
}
}
},
"schema": {
"properties": {
"segments": {
"items": {
"$ref": "#/components/schemas/contactdb_segments"
},
"type": "array"
}
},
"required": [
"segments"
],
"title": "List All Segments response",
"type": "object"
}
}
},
"description": ""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve all segments",
"tags": [
"Contacts API - Segments"
],
"x-stoplight": {
"id": "GET_contactdb-segments",
"mock": {
"dynamic": false
},
"public": true
}
},
"post": {
"description": "**This endpoint allows you to create a new segment.**\n\n\n Valid operators for create and update depend on the type of the field for which you are searching.\n\n**Dates**\n- \"eq\", \"ne\", \"lt\" (before), \"gt\" (after)\n - You may use MM/DD/YYYY for day granularity or an epoch for second granularity.\n- \"empty\", \"not_empty\"\n- \"is within\"\n - You may use an [ISO 8601 date format](https://en.wikipedia.org/wiki/ISO_8601) or the # of days.\n\n**Text**\n- \"contains\"\n- \"eq\" (is/equals - matches the full field)\n- \"ne\" (is not/not equals - matches any field where the entire field is not the condition value)\n- \"empty\"\n- \"not_empty\"\n\n**Numbers**\n- \"eq\" (is/equals)\n- \"lt\" (is less than)\n- \"gt\" (is greater than)\n- \"empty\"\n- \"not_empty\"\n\n**Email Clicks and Opens**\n- \"eq\" (opened)\n- \"ne\" (not opened)\n\nAll field values must be a string.\n\n\nConditions using \"eq\" or \"ne\" for email clicks and opens should provide a \"field\" of either `clicks.campaign_identifier` or `opens.campaign_identifier`.\nThe condition value should be a string containing the id of a completed campaign.\n\n\nThe conditions list may contain multiple conditions, joined by an \"and\" or \"or\" in the \"and_or\" field.\n\nThe first condition in the conditions list must have an empty \"and_or\", and subsequent conditions must all specify an \"and_or\".",
"operationId": "POST_contactdb-segments",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/contactdb_segments"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"conditions": [
{
"and_or": "",
"field": "last_name",
"operator": "eq",
"value": "Miller"
},
{
"and_or": "and",
"field": "last_clicked",
"operator": "gt",
"value": "01/02/2015"
},
{
"and_or": "or",
"field": "clicks.campaign_identifier",
"operator": "eq",
"value": "513"
}
],
"id": 1,
"list_id": 4,
"name": "Last Name Miller",
"recipient_count": 0
}
}
},
"schema": {
"$ref": "#/components/schemas/contactdb_segments_with_id"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"message": "request body is not valid json"
},
{
"message": "invalid value is passed into one of the request body parameters"
},
{
"field": "field",
"message": "field and set value is not passed into the request body"
},
{
"field": "value",
"message": "value and set value is not passed into the request body"
},
{
"field": "operator",
"message": "operator and set value is not passed into the request body"
},
{
"field": "and_or",
"message": "and_or is not set on more than one condition and less than all conditions"
},
{
"field": "and_or",
"message": "and_or is set on all conditions"
},
{
"field": "and_or",
"message": "and_or is set on the only condition passed"
},
{
"field": "and_or",
"message": "and_or and set value is not passed into the request body"
},
{
"field": "list_id",
"message": "the list_id is not valid"
},
{
"field": "name",
"message": "the name is not valid"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"name\" : \"Returned if the name is not valid\"\n\"list_id\" : \"Returned if the list_id is not valid\"\n\"and_or\" : \"Returned if and_or and set value is not passed into the request body\"\n\"and_or\" : \"Returned if and_or is set on the only condition passed\"\n\"and_or\" : \"Returned if and_or is set on all conditions\"\n\"and_or\" : \"Returned if and_or is not set on more than one condition and less than all conditions\"\n\"operator\" : \"Returned if operator and set value is not passed into the request body\"\n\"value\" : \"Returned if value and set value is not passed into the request body\"\n\"field\" : \"Returned if field and set value is not passed into the request body\"\n\"\" : \"Returned if request body is not valid json\"\n\"\" : \"Returned if invalid value is passed into one of the request body parameters\""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Create a Segment",
"tags": [
"Contacts API - Segments"
],
"x-stoplight": {
"id": "POST_contactdb-segments",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/contactdb/segments/{segment_id}": {
"delete": {
"description": "**This endpoint allows you to delete a segment from your recipients database.**\n\nYou also have the option to delete all the contacts from your Marketing Campaigns recipient database who were in this segment.",
"operationId": "DELETE_contactdb-segments-segment_id",
"parameters": [
{
"description": "True to delete all contacts matching the segment in addition to deleting the segment",
"in": "query",
"name": "delete_contacts",
"schema": {
"type": "boolean"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"$ref": "#/components/requestBodies/DELETE_contactdb-lists-list_idBody"
},
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"nullable": true
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": "segment_id",
"message": "Returned if segment_id is not valid"
},
{
"field": "delete_contacts",
"message": "Returned if delete_contacts is not a valid boolean"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"segment_id\" : \"Returned if segment_id is not valid\"\n\"delete_contacts\" : \"Returned if delete_contacts is not a valid boolean\""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": "segment_id",
"message": "segment_id does not exist"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"segment_id\" : \"Returned if segment_id does not exist\""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete a segment",
"tags": [
"Contacts API - Segments"
],
"x-stoplight": {
"id": "DELETE_contactdb-segments-segmentid",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 204
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve a single segment with the given ID.**",
"operationId": "GET_contactdb-segments-segment_id",
"parameters": [
{
"description": "The ID of the segment you want to request.",
"in": "query",
"name": "segment_id",
"required": true,
"schema": {
"type": "integer"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"conditions": [
{
"and_or": "",
"field": "last_name",
"operator": "eq",
"value": "Miller"
}
],
"id": 1,
"list_id": 4,
"name": "Last Name Miller",
"recipient_count": 1
}
}
},
"schema": {
"$ref": "#/components/schemas/contactdb_segments"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"message": "if segment_id is not valid"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"segment_id\" : \"Returned if segment_id is not valid\""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"message": "segment_id not found"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "\"segment_id\" : \"Returned if segment_id does not exist\""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve a segment",
"tags": [
"Contacts API - Segments"
],
"x-stoplight": {
"id": "GET_contactdb-segments-segmentid",
"mock": {
"dynamic": false
},
"public": true
}
},
"parameters": [
{
"in": "path",
"name": "segment_id",
"required": true,
"schema": {
"type": "string"
}
}
],
"patch": {
"description": "**This endpoint allows you to update a segment.**",
"operationId": "PATCH_contactdb-segments-segment_id",
"parameters": [
{
"description": "The ID of the segment you are updating.",
"in": "query",
"name": "segment_id",
"schema": {
"type": "string"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"conditions": [
{
"and_or": "",
"field": "last_name",
"operator": "eq",
"value": "Miller"
}
],
"list_id": 5,
"name": "The Millers"
},
"properties": {
"conditions": {
"description": "The conditions by which this segment should be created.",
"items": {
"$ref": "#/components/schemas/contactdb_segments_conditions"
},
"type": "array"
},
"list_id": {
"description": "The list ID you would like this segment to be built from.",
"type": "number"
},
"name": {
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"conditions": [
{
"and_or": "",
"field": "last_name",
"operator": "eq",
"value": "Miller"
}
],
"id": 5,
"list_id": 5,
"name": "The Millers",
"recipient_count": 1
}
}
},
"schema": {
"$ref": "#/components/schemas/contactdb_segments"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"message": "request body is not valid json"
},
{
"message": "invalid value is passed into one of the request body parameters"
},
{
"message": "segment id is not valid",
"segment_id": "segment_id"
},
{
"field": "field",
"message": "field and set value is not passed into the request body"
},
{
"field": "value",
"message": "value and set value is not passed into the request body"
},
{
"field": "operator",
"message": "operator and set value is not passed into the request body"
},
{
"field": "and_or",
"message": "and_or is not set on more than one condition and less than all conditions"
},
{
"field": "and_or",
"message": "and_or is set on all conditions"
},
{
"field": "and_or",
"message": "and_or is set on the only condition passed"
},
{
"field": "and_or",
"message": "and_or and set value is not passed into the request body"
},
{
"field": "list_id",
"message": "the list_id is not valid"
},
{
"field": "name",
"message": "the name is not valid"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update a segment",
"tags": [
"Contacts API - Segments"
],
"x-stoplight": {
"id": "PATCH_contactdb-segments-segmentid",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": false
}
}
},
"/contactdb/segments/{segment_id}/recipients": {
"get": {
"description": "**This endpoint allows you to retrieve all of the recipients in a segment with the given ID.**",
"operationId": "GET_contactdb-segments-segment_id-recipients",
"parameters": [
{
"in": "query",
"name": "page",
"schema": {
"type": "integer"
}
},
{
"in": "query",
"name": "page_size",
"schema": {
"type": "integer"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"recipients": [
{
"created_at": 1422313607,
"custom_fields": [
{
"id": 23,
"name": "pet",
"type": "text",
"value": "Indiana"
}
],
"email": "jones@example.com",
"first_name": null,
"id": "YUBh",
"last_clicked": null,
"last_emailed": null,
"last_name": "Jones",
"last_opened": null,
"updated_at": 1422313790
}
]
}
}
},
"schema": {
"properties": {
"recipients": {
"items": {
"$ref": "#/components/schemas/contactdb_recipient"
},
"type": "array"
}
},
"required": [
"recipients"
],
"title": "List Recipients On a Segment response",
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": "\"page\" : \"Returned if page is not a valid integer\"\n\"page\" : \"Returned if page is less than 1\"\n\"page_size\" : \"Returned if page_size is not a valid integer\""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": "\"segment_id\" : \"Returned if segment_id is not valid\"\n\"segment_id\" : \"Returned if segment_id does not exist\""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve recipients on a segment",
"tags": [
"Contacts API - Segments"
],
"x-stoplight": {
"id": "GET_contactdb-segments-segmentid-recipients",
"mock": {
"dynamic": false
},
"public": true
}
},
"parameters": [
{
"description": "The ID of the segment from which you want to retrieve recipients.",
"in": "path",
"name": "segment_id",
"required": true,
"schema": {
"type": "integer"
}
}
]
},
"/contactdb/status": {
"get": {
"description": "**This endpoint allows you to check the upload status of a Marketing Campaigns recipient.**",
"operationId": "GET_contactdb-status",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"status": [
{
"id": "worker_delay",
"value": "delayed"
},
{
"id": "worker_delay_seconds",
"value": "75.0"
}
]
}
}
},
"schema": {
"properties": {
"status": {
"items": {
"properties": {
"": {
"type": "string"
},
"id": {
"default": "",
"description": "Valid values are \"worker_delay\" and \"worker_delay_seconds\" (the second value appears only if \"worker_delay\" has a value of \"delayed\").",
"type": "string"
},
"value": {
"default": "",
"description": "Valid values for the ID \"worker_delay\" are \"OK\" or \"Delayed\". Valid values for the ID \"worker_delay_seconds\" is the time of delay to upload.",
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Get Recipient Upload Status",
"tags": [
"Contacts API - Recipients"
],
"x-stoplight": {
"id": "GET_contactdb-status",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/designs": {
"get": {
"description": "**This endpoint allows you to retrieve a list of designs already stored in your Design Library**.\n\nA GET request to `/designs` will return a list of your existing designs. This endpoint will not return the pre-built Twilio SendGrid designs. Pre-built designs can be retrieved using the `/designs/pre-builts` endpoint, which is detailed below.\n\nBy default, you will receive 100 results per request; however, you can modify the number of results returned by passing an integer to the `page_size` query parameter.",
"operationId": "LIST-designs",
"parameters": [
{
"$ref": "#/components/parameters/trait_designsQueryStrings_page_size"
},
{
"$ref": "#/components/parameters/trait_designsQueryStrings_page_token"
},
{
"$ref": "#/components/parameters/trait_designsQueryStrings_summary"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"_metadata": {
"count": 3,
"self": "https://api.sendgrid.com/v3/designs?page_token=vHdvHCg0w1F-TmWJcPNpTEnFY2aPEmRBHONwOgZ6TgJbX2_I"
},
"result": [
{
"categories": [
"welcome",
"new customer"
],
"created_at": "2021-04-09T17:29:46Z",
"editor": "code",
"generate_plain_content": true,
"id": "3247eaea-c912-42a3-b0bc-60bdaf210396",
"name": "Welcome Email",
"subject": "Welcom to the Cake or Pie Cafe",
"thumbnail_url": "//us-east-2-production-thumbnail-bucket.s3.amazonaws.com/llny8o5b3m636z92p7hbjnmq1jvpka39p370jwtin2s1wxv7x1sgm0y5fk518d0s.png",
"updated_at": "2021-04-09T17:29:55Z"
},
{
"categories": [
"promo",
"coupon"
],
"created_at": "2021-04-09T17:29:21Z",
"editor": "design",
"generate_plain_content": true,
"id": "02dfd792-f31f-439a-a79e-5c47fbcfdbac",
"name": "Monthly Promo",
"subject": "Free Dozen Cupcakes",
"thumbnail_url": "//us-east-2-production-thumbnail-bucket.s3.amazonaws.com/hfyxahd7ues2ajuoeoqq2xe6ibdasl1q89ox0y9ncya2ftpoicssmtf9ddus4c39.png",
"updated_at": "2021-04-09T17:29:42Z"
},
{
"categories": [],
"created_at": "2020-10-09T17:33:46Z",
"editor": "design",
"generate_plain_content": true,
"id": "e54be823-19ef-4c6f-8b60-efd7514f492d",
"name": "Duplicate: Ingrid & Anders",
"subject": "Welcome to Ingrid & Anders!",
"thumbnail_url": "//us-east-2-production-thumbnail-bucket.s3.amazonaws.com/12kni9gjpyb9uxmwr9vk7unycjr70u95zoyhe9sg2zounul2zg7dih1s20k13q2z.png",
"updated_at": "2021-04-07T19:57:52Z"
}
]
}
}
},
"schema": {
"properties": {
"_metadata": {
"$ref": "#/components/schemas/_metadata"
},
"result": {
"items": {
"$ref": "#/components/schemas/design-output-summary"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "List Designs",
"tags": [
"Designs API"
],
"x-stoplight": {
"id": "LIST-designs",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"post": {
"description": "**This endpoint allows you to create a new design**.\n\nYou can add a new design by passing data, including a string of HTML email content, to `/designs`. When creating designs from scratch, be aware of the styling constraints inherent to many email clients. For a list of best practices, see our guide to [Cross-Platform Email Design](https://sendgrid.com/docs/ui/sending-email/cross-platform-html-design/).\n\nThe Design Library can also convert your design’s HTML elements into drag and drop modules that are editable in the Designs Library user interface. For more, visit the [Design and Code Editor documentation](https://sendgrid.com/docs/ui/sending-email/editor/#drag--drop-markup).\n\nBecause the `/designs` endpoint makes it easy to add designs, you can create a design with your preferred tooling or migrate designs you already own without relying on the Design Library UI.",
"operationId": "POST-design",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/design-input"
}
}
}
},
"responses": {
"201": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"categories": [],
"created_at": "2021-04-30T18:51:20Z",
"editor": "design",
"generate_plain_content": false,
"html_content": "\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ",
"id": "3247eaea-c912-42a3-b0bc-60bdaf210396",
"name": "Ahoy, World!",
"plain_content": "Ahoy, World!\n\n{{Sender_Name}}\n\n{{Sender_Address}} , {{Sender_City}} , {{Sender_State}} {{Sender_Zip}}\n\nUnsubscribe ( {{{unsubscribe}}} ) - Unsubscribe Preferences ( {{{unsubscribe_preferences}}} )",
"subject": "Getting Started",
"thumbnail_url": "//us-east-2-production-thumbnail-bucket.s3.amazonaws.com/kjlrmded0qnrscv8zqr39npoimrpdwgiax59q8iq6ovj7yoks2fzxoxpfjpwph6o.png",
"updated_at": "2021-04-30T18:51:20Z"
}
}
},
"schema": {
"$ref": "#/components/schemas/design-output"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/api-errors"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Create Design",
"tags": [
"Designs API"
],
"x-stoplight": {
"id": "POST-design",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/designs/pre-builts": {
"get": {
"description": "**This endpoint allows you to retrieve a list of pre-built designs provided by Twilio SendGrid**.\n\nUnlike the `/designs` endpoint where *your* designs are stored, a GET request made to `designs/pre-builts` will retrieve a list of the pre-built Twilio SendGrid designs. This endpoint will not return the designs stored in your Design Library.\n\nBy default, you will receive 100 results per request; however, you can modify the number of results returned by passing an integer to the `page_size` query parameter.\n\nThis endpoint is useful for retrieving the IDs of Twilio SendGrid designs that you want to duplicate and modify.",
"operationId": "LIST-Sendgrid-Pre-built-designs",
"parameters": [
{
"$ref": "#/components/parameters/trait_designsQueryStrings_page_size"
},
{
"$ref": "#/components/parameters/trait_designsQueryStrings_page_token"
},
{
"$ref": "#/components/parameters/trait_designsQueryStrings_summary"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"_metadata": {
"count": 6,
"self": "https://api.sendgrid.com/v3/designs/pre-builts?page_token=yYzyCxj-iIVgP54t6NjKkunDCKYLLpngo-5vAsfYXz0To34U"
},
"result": [
{
"categories": [],
"created_at": "2019-09-10T02:11:34Z",
"editor": "design",
"generate_plain_content": true,
"id": "6ad69134-7165-48cb-964a-6c3cf03e8af8",
"name": "Off Grid Adventures",
"subject": "Welcome to the family!",
"thumbnail_url": "//us-east-2-production-thumbnail-bucket.s3.amazonaws.com/a85b4b202ff28094828f11ff472360caecf67ead2d186b69b45c904b9251aa0b.png",
"updated_at": "2021-01-11T21:47:52Z"
},
{
"categories": [],
"created_at": "2019-09-10T02:12:32Z",
"editor": "design",
"generate_plain_content": true,
"id": "b0a9c6f7-a9a1-4b52-b0c5-16fc6f4cdb2b",
"name": "Song Riddle",
"subject": "Welcome to Song Riddle!",
"thumbnail_url": "//us-east-2-production-thumbnail-bucket.s3.amazonaws.com/4ef3a39249f3accb8461b03950c071454a745a232508feca89a626b3e7f578d3.png",
"updated_at": "2021-01-11T21:46:43Z"
},
{
"categories": [],
"created_at": "2019-09-10T02:10:38Z",
"editor": "design",
"generate_plain_content": true,
"id": "f8d8da76-bcca-4cfe-b809-733887855f57",
"name": "Ingrid & Anders 1",
"subject": "Welcome to Ingrid & Anders!",
"thumbnail_url": "//us-east-2-production-thumbnail-bucket.s3.amazonaws.com/15c97ffa97ee31693581a67526728d096eef00adfbaa34bb030d91034d477da4.png",
"updated_at": "2021-01-11T21:45:05Z"
},
{
"categories": [],
"created_at": "2019-09-10T02:09:31Z",
"editor": "design",
"generate_plain_content": true,
"id": "2935a7d0-7f02-4e0f-a570-dc302ce09749",
"name": "Ingrid & Anders 2",
"subject": "Check out these exclusive deals!",
"thumbnail_url": "//us-east-2-production-thumbnail-bucket.s3.amazonaws.com/7b36a6c0955cab0c350d105114ad248700a685bd11032592cdef85ae26540afc.png",
"updated_at": "2021-01-11T21:44:08Z"
},
{
"categories": [],
"created_at": "2019-09-10T02:08:29Z",
"editor": "design",
"generate_plain_content": true,
"id": "7826ef14-7ba6-4dbc-91f0-a8c610ebe962",
"name": "Ingrid & Anders 3",
"subject": "Join our VIP club and save big!",
"thumbnail_url": "//us-east-2-production-thumbnail-bucket.s3.amazonaws.com/6dd8dd73a1a62bd7a76c4313b52d7c749250d49e31b19cce718906655fcbc675.png",
"updated_at": "2021-01-11T21:41:35Z"
},
{
"categories": [],
"created_at": "2019-09-10T02:03:06Z",
"editor": "design",
"generate_plain_content": true,
"id": "41da47e7-d3e2-491b-a83f-f499a4139d6a",
"name": "Mercado",
"subject": "Subject",
"thumbnail_url": "//us-east-2-production-thumbnail-bucket.s3.amazonaws.com/9cc87cc7671719712d9d363184995d0ec05103355db300ff03641fe9e205651d.png",
"updated_at": "2021-01-11T21:39:23Z"
}
]
}
}
},
"schema": {
"properties": {
"_metadata": {
"$ref": "#/components/schemas/_metadata"
},
"result": {
"items": {
"$ref": "#/components/schemas/design-output-summary"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "List SendGrid Pre-built Designs",
"tags": [
"Designs API"
],
"x-stoplight": {
"id": "LIST-Sendgrid-Pre-built-designs",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/designs/pre-builts/{id}": {
"get": {
"description": "**This endpoint allows you to retrieve a single pre-built design**.\n\nA GET request to `/designs/pre-builts/{id}` will retrieve details about a specific pre-built design.\n\nThis endpoint is valuable when retrieving details about a pre-built design that you wish to duplicate and modify.",
"operationId": "GET-sendgrid-pre-built-design",
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"categories": [],
"created_at": "2019-09-10T02:11:34Z",
"editor": "design",
"generate_plain_content": true,
"html_content": "\n\n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n You've found the secret!
\n \n \n
\n \n \n \n
\n \n \n \n
\n \n You've found a community of travelers that are just like you.
\n
\n
We don't want to be stuck in tourist traps that isolate us from vibrant, local experiences. We want to discover the hidden gems and less-traveled roads of our next destination.
\n
\n
Ready for your next authentic travel experience?
\n \n
\n \n \n \n
\n \n
\n \n \n \n
\n \n \n
\n \n \n
\n
\n \n \n ",
"id": "6ad69134-7165-48cb-964a-6c3cf03e8af8",
"name": "Off Grid Adventures",
"plain_content": "You've found the secret!\n\nWelcome to the family!\n\nYou've found a community of travelers that are just like you.\n\nWe don't want to be stuck in tourist traps that isolate us from vibrant, local experiences. We want to discover the hidden gems and less-traveled roads of our next destination.\n\nReady for your next authentic travel experience?\n\nBrowse Gallery\n\n( https://www.facebook.com/sendgrid/ ) ( https://twitter.com/sendgrid?ref_src=twsrc%5egoogle%7ctwcamp%5eserp%7ctwgr%5eauthor ) ( https://www.instagram.com/sendgrid/?hl=en ) ( https://www.pinterest.com/sendgrid/ )\n\n{{Sender_Name}}\n\n{{Sender_Address}} , {{Sender_City}} , {{Sender_State}} {{Sender_Zip}}\n\nUnsubscribe ( {{{unsubscribe}}} ) - Unsubscribe Preferences ( {{{unsubscribe_preferences}}} )",
"subject": "Welcome to the family!",
"thumbnail_url": "//us-east-2-production-thumbnail-bucket.s3.amazonaws.com/a85b4b202ff28094828f11ff472360caecf67ead2d186b69b45c904b9251aa0b.png",
"updated_at": "2021-01-11T21:47:52Z"
}
}
},
"schema": {
"$ref": "#/components/schemas/design-output"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/api-errors"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/api-errors"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Get SendGrid Pre-built Design",
"tags": [
"Designs API"
],
"x-stoplight": {
"id": "GET-sendgrid-pre-built-design",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"description": "The ID of the pre-built Design you want to duplicate.",
"in": "path",
"name": "id",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"post": {
"description": "**This endpoint allows you to duplicate one of the pre-built Twilio SendGrid designs**.\n\nLike duplicating one of your existing designs, you are not required to pass any data in the body of a request to this endpoint. If you choose to leave the `name` field blank, your duplicate will be assigned the name of the design it was copied from with the text \"Duplicate: \" prepended to it. This name change is only a convenience, as the duplicate design will be assigned a unique ID that differentiates it from your other designs. You can retrieve the IDs for Twilio SendGrid pre-built designs using the \"List SendGrid Pre-built Designs\" endpoint.\n\nYou can modify your duplicate’s name at the time of creation by passing an updated value to the `name` field when making the initial request.\nMore on retrieving design IDs can be found above.",
"operationId": "POST-sendgrid-pre-built-design",
"requestBody": {
"$ref": "#/components/requestBodies/design-duplicate-input"
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"categories": [],
"created_at": "2021-04-30T19:15:28Z",
"editor": "design",
"generate_plain_content": true,
"html_content": "\n\n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n You've found the secret!
\n \n \n
\n \n \n \n
\n \n \n \n
\n \n You've found a community of travelers that are just like you.
\n
\n
We don't want to be stuck in tourist traps that isolate us from vibrant, local experiences. We want to discover the hidden gems and less-traveled roads of our next destination.
\n
\n
Ready for your next authentic travel experience?
\n \n
\n \n \n \n
\n \n
\n \n \n \n
\n \n \n
\n \n \n
\n
\n \n \n ",
"id": "abe0877f-a224-21e2-b62e-c789d326cda5",
"name": "Ahoy, Pre-built Design!",
"plain_content": "You've found the secret!\n\nWelcome to the family!\n\nYou've found a community of travelers that are just like you.\n\nWe don't want to be stuck in tourist traps that isolate us from vibrant, local experiences. We want to discover the hidden gems and less-traveled roads of our next destination.\n\nReady for your next authentic travel experience?\n\nBrowse Gallery\n\n( https://www.facebook.com/sendgrid/ ) ( https://twitter.com/sendgrid?ref_src=twsrc%5egoogle%7ctwcamp%5eserp%7ctwgr%5eauthor ) ( https://www.instagram.com/sendgrid/?hl=en ) ( https://www.pinterest.com/sendgrid/ )\n\n{{Sender_Name}}\n\n{{Sender_Address}} , {{Sender_City}} , {{Sender_State}} {{Sender_Zip}}\n\nUnsubscribe ( {{{unsubscribe}}} ) - Unsubscribe Preferences ( {{{unsubscribe_preferences}}} )",
"subject": "Welcome to the family!",
"thumbnail_url": "//us-east-2-production-thumbnail-bucket.s3.amazonaws.com/a85b4b202ff28094828f11ff472360caecf67ead2d186b69b45c904b9251aa0b.png",
"updated_at": "2021-04-30T19:15:28Z"
}
}
},
"schema": {
"$ref": "#/components/schemas/design-output"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/api-errors"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/api-errors"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Duplicate SendGrid Pre-built Design",
"tags": [
"Designs API"
],
"x-stoplight": {
"id": "POST-sendgrid-pre-built-design",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/designs/{id}": {
"delete": {
"description": "**This endpoint allows you to delete a single design**.\n\nBe sure to check the ID of the design you intend to delete before making this request; deleting a design is a permanent action.",
"operationId": "DELETE-design",
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/api-errors"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/api-errors"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete Design",
"tags": [
"Designs API"
],
"x-stoplight": {
"id": "DELETE-design",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve a single design**.\n\nA GET request to `/designs/{id}` will retrieve details about a specific design in your Design Library.\n\nThis endpoint is valuable when retrieving information stored in a field that you wish to update using a PATCH request.",
"operationId": "GET-design",
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"categories": [],
"created_at": "2021-04-30T18:51:20Z",
"editor": "design",
"generate_plain_content": false,
"html_content": "\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ",
"id": "15b85720-ce48-48ef-8a07-672b0d3455da",
"name": "Ahoy, World!",
"plain_content": "Ahoy, World!\n\n{{Sender_Name}}\n\n{{Sender_Address}} , {{Sender_City}} , {{Sender_State}} {{Sender_Zip}}\n\nUnsubscribe ( {{{unsubscribe}}} ) - Unsubscribe Preferences ( {{{unsubscribe_preferences}}} )",
"subject": "Getting Started",
"thumbnail_url": "//us-east-2-production-thumbnail-bucket.s3.amazonaws.com/5yysvuyi1lpdnxo1ym8ax8yd7ompve3azjtme76gamdace01vko3eyn1kzso1lhy.png",
"updated_at": "2021-04-30T18:51:20Z"
}
}
},
"schema": {
"$ref": "#/components/schemas/design-output"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/api-errors"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/api-errors"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Get Design",
"tags": [
"Designs API"
],
"x-stoplight": {
"id": "GET-design",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"description": "The ID of the Design you want to duplicate.",
"in": "path",
"name": "id",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"patch": {
"description": "**This endpoint allows you to edit a design**.\n\nThe Design API supports PATCH requests, which allow you to make partial updates to a single design. Passing data to a specific field will update only the data stored in that field; all other fields will be unaltered.\n\nFor example, updating a design's name requires that you make a PATCH request to this endpoint with data specified for the `name` field only.\n\n```\n{\n \"name\": \"\"\n}\n```",
"operationId": "PUT-design",
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"categories": [
"promotions"
],
"generate_plain_content": false,
"html_content": "\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ",
"name": "Ahoy, World!",
"subject": "Getting Started"
},
"properties": {
"categories": {
"description": "The list of categories applied to the design",
"items": {
"maxLength": 255,
"type": "string"
},
"maxItems": 10,
"type": "array",
"uniqueItems": true
},
"generate_plain_content": {
"default": true,
"description": "If true, plain_content is always generated from html_content. If false, plain_content is not altered.",
"type": "boolean"
},
"html_content": {
"description": "The HTML content of the Design.",
"maxLength": 1048576,
"type": "string"
},
"name": {
"default": "My Design",
"description": "Name of the Design.",
"maxLength": 100,
"type": "string"
},
"plain_content": {
"default": "",
"description": "Plain text content of the Design.",
"maxLength": 1048576,
"type": "string"
},
"subject": {
"description": "Subject of the Design.",
"maxLength": 5000,
"type": "string"
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"categories": [
"promotions"
],
"created_at": "2021-04-30T18:51:20Z",
"editor": "design",
"generate_plain_content": false,
"html_content": "\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ",
"id": "15b85720-ce48-48ef-8a07-672b0d3455da",
"name": "Ahoy, World!",
"subject": "Getting Started",
"thumbnail_url": "//us-east-2-production-thumbnail-bucket.s3.amazonaws.com/5yysvuyi1lpdnxo1ym8ax8yd7ompve3azjtme76gamdace01vko3eyn1kzso1lhy.png",
"updated_at": "2021-04-30T18:51:20Z"
}
}
},
"schema": {
"$ref": "#/components/schemas/design-output"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/api-errors"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/api-errors"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update Design",
"tags": [
"Designs API"
],
"x-stoplight": {
"id": "PUT-design",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"post": {
"description": "**This endpoint allows you to duplicate one of your existing designs**.\n\nModifying an existing design is often the easiest way to create something new.\n\nYou are not required to pass any data in the body of a request to this endpoint. If you choose to leave the `name` field blank, your duplicate will be assigned the name of the design it was copied from with the text \"Duplicate: \" prepended to it. This name change is only a convenience, as the duplicate will be assigned a unique ID that differentiates it from your other designs.\n\nYou can modify your duplicate’s name at the time of creation by passing an updated value to the `name` field when making the initial request.\nMore on retrieving design IDs can be found below.",
"operationId": "POST-design-dup",
"requestBody": {
"$ref": "#/components/requestBodies/design-duplicate-input"
},
"responses": {
"201": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"categories": [],
"created_at": "2021-04-30T19:00:16Z",
"editor": "design",
"generate_plain_content": false,
"html_content": "\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ",
"id": "15b85720-ce48-48ef-8a07-672b0d3455da",
"name": "Ahoy, Cake or Pie Cafe!",
"plain_content": "Ahoy, World!\n\n{{Sender_Name}}\n\n{{Sender_Address}} , {{Sender_City}} , {{Sender_State}} {{Sender_Zip}}\n\nUnsubscribe ( {{{unsubscribe}}} ) - Unsubscribe Preferences ( {{{unsubscribe_preferences}}} )",
"subject": "Getting Started",
"thumbnail_url": "//us-east-2-production-thumbnail-bucket.s3.amazonaws.com/79bb769ae6464960a307040120ad6f9921896fe9825e845ad1f24d12285ea068.png",
"updated_at": "2021-04-30T19:00:16Z"
}
}
},
"schema": {
"$ref": "#/components/schemas/design-output"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/api-error"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/api-errors"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Duplicate Design",
"tags": [
"Designs API"
],
"x-stoplight": {
"id": "POST-design",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/devices/stats": {
"get": {
"description": "**This endpoint allows you to retrieve your email statistics segmented by the device type.**\n\n**We only store up to 7 days of email activity in our database.** By default, 500 items will be returned per request via the Advanced Stats API endpoints.\n\n## Available Device Types\n| **Device** | **Description** | **Example** |\n|---|---|---|\n| Desktop | Email software on desktop computer. | I.E., Outlook, Sparrow, or Apple Mail. |\n| Webmail |\tA web-based email client. | I.E., Yahoo, Google, AOL, or Outlook.com. |\n| Phone | A smart phone. | iPhone, Android, Blackberry, etc.\n| Tablet | A tablet computer. | iPad, android based tablet, etc. |\n| Other | An unrecognized device. |\n\nAdvanced Stats provide a more in-depth view of your email statistics and the actions taken by your recipients. You can segment these statistics by geographic location, device type, client type, browser, and mailbox provider. For more information about statistics, please see our [Statistics Overview](https://sendgrid.com/docs/ui/analytics-and-reporting/stats-overview/).",
"operationId": "GET_devices-stats",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
},
{
"$ref": "#/components/parameters/trait_statsAdvancedQueryStringsLimitOffset_limit"
},
{
"$ref": "#/components/parameters/trait_statsAdvancedQueryStringsLimitOffset_offset"
},
{
"$ref": "#/components/parameters/trait_statsAdvancedQueryStringsLimitOffset_aggregated_by"
},
{
"$ref": "#/components/parameters/trait_statsAdvancedQueryStringsLimitOffset_start_date"
},
{
"$ref": "#/components/parameters/trait_statsAdvancedQueryStringsLimitOffset_end_date"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"date": "2015-10-11",
"stats": [
{
"metrics": {
"opens": 0,
"unique_opens": 0
},
"name": "Webmail",
"type": "device"
}
]
},
{
"date": "2015-10-12",
"stats": [
{
"metrics": {
"opens": 0,
"unique_opens": 0
},
"name": "Webmail",
"type": "device"
}
]
},
{
"date": "2015-10-13",
"stats": [
{
"metrics": {
"opens": 0,
"unique_opens": 0
},
"name": "Webmail",
"type": "device"
}
]
},
{
"date": "2015-10-14",
"stats": [
{
"metrics": {
"opens": 0,
"unique_opens": 0
},
"name": "Webmail",
"type": "device"
}
]
},
{
"date": "2015-10-15",
"stats": [
{
"metrics": {
"opens": 0,
"unique_opens": 0
},
"name": "Webmail",
"type": "device"
}
]
},
{
"date": "2015-10-16",
"stats": [
{
"metrics": {
"opens": 0,
"unique_opens": 0
},
"name": "Webmail",
"type": "device"
}
]
},
{
"date": "2015-10-17",
"stats": [
{
"metrics": {
"opens": 0,
"unique_opens": 0
},
"name": "Webmail",
"type": "device"
}
]
},
{
"date": "2015-10-18",
"stats": [
{
"metrics": {
"opens": 0,
"unique_opens": 0
},
"name": "Webmail",
"type": "device"
}
]
},
{
"date": "2015-10-19",
"stats": [
{
"metrics": {
"opens": 0,
"unique_opens": 0
},
"name": "Webmail",
"type": "device"
}
]
},
{
"date": "2015-10-20",
"stats": [
{
"metrics": {
"opens": 0,
"unique_opens": 0
},
"name": "Webmail",
"type": "device"
}
]
},
{
"date": "2015-10-21",
"stats": [
{
"metrics": {
"opens": 1,
"unique_opens": 1
},
"name": "Webmail",
"type": "device"
}
]
},
{
"date": "2015-10-22",
"stats": [
{
"metrics": {
"opens": 0,
"unique_opens": 0
},
"name": "Webmail",
"type": "device"
}
]
},
{
"date": "2015-10-23",
"stats": [
{
"metrics": {
"opens": 0,
"unique_opens": 0
},
"name": "Webmail",
"type": "device"
}
]
},
{
"date": "2015-10-24",
"stats": [
{
"metrics": {
"opens": 0,
"unique_opens": 0
},
"name": "Webmail",
"type": "device"
}
]
},
{
"date": "2015-10-25",
"stats": [
{
"metrics": {
"opens": 0,
"unique_opens": 0
},
"name": "Webmail",
"type": "device"
}
]
},
{
"date": "2015-10-26",
"stats": [
{
"metrics": {
"opens": 2,
"unique_opens": 2
},
"name": "Webmail",
"type": "device"
}
]
},
{
"date": "2015-10-27",
"stats": [
{
"metrics": {
"opens": 0,
"unique_opens": 0
},
"name": "Webmail",
"type": "device"
}
]
},
{
"date": "2015-10-28",
"stats": [
{
"metrics": {
"opens": 0,
"unique_opens": 0
},
"name": "Webmail",
"type": "device"
}
]
},
{
"date": "2015-10-29",
"stats": [
{
"metrics": {
"opens": 0,
"unique_opens": 0
},
"name": "Webmail",
"type": "device"
}
]
},
{
"date": "2015-10-30",
"stats": [
{
"metrics": {
"opens": 0,
"unique_opens": 0
},
"name": "Webmail",
"type": "device"
}
]
},
{
"date": "2015-10-31",
"stats": [
{
"metrics": {
"opens": 0,
"unique_opens": 0
},
"name": "Webmail",
"type": "device"
}
]
},
{
"date": "2015-11-01",
"stats": [
{
"metrics": {
"opens": 0,
"unique_opens": 0
},
"name": "Webmail",
"type": "device"
}
]
},
{
"date": "2015-11-02",
"stats": [
{
"metrics": {
"opens": 0,
"unique_opens": 0
},
"name": "Webmail",
"type": "device"
}
]
},
{
"date": "2015-11-03",
"stats": [
{
"metrics": {
"opens": 0,
"unique_opens": 0
},
"name": "Webmail",
"type": "device"
}
]
},
{
"date": "2015-11-04",
"stats": [
{
"metrics": {
"opens": 0,
"unique_opens": 0
},
"name": "Webmail",
"type": "device"
}
]
},
{
"date": "2015-11-05",
"stats": [
{
"metrics": {
"opens": 0,
"unique_opens": 0
},
"name": "Webmail",
"type": "device"
}
]
},
{
"date": "2015-11-06",
"stats": [
{
"metrics": {
"opens": 0,
"unique_opens": 0
},
"name": "Webmail",
"type": "device"
}
]
},
{
"date": "2015-11-07",
"stats": [
{
"metrics": {
"opens": 0,
"unique_opens": 0
},
"name": "Webmail",
"type": "device"
}
]
},
{
"date": "2015-11-08",
"stats": [
{
"metrics": {
"opens": 0,
"unique_opens": 0
},
"name": "Webmail",
"type": "device"
}
]
},
{
"date": "2015-11-09",
"stats": [
{
"metrics": {
"opens": 0,
"unique_opens": 0
},
"name": "Webmail",
"type": "device"
}
]
},
{
"date": "2015-11-10",
"stats": [
{
"metrics": {
"opens": 0,
"unique_opens": 0
},
"name": "Webmail",
"type": "device"
}
]
}
]
}
},
"schema": {
"items": {
"properties": {
"date": {
"description": "The date that the statistics were gathered.",
"type": "string"
},
"stats": {
"description": "The list of statistics.",
"items": {
"properties": {
"metrics": {
"$ref": "#/components/schemas/advanced_stats_opens"
},
"name": {
"description": "The name of the specific segmentation.",
"type": "string"
},
"type": {
"description": "The type of segmentation.",
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve email statistics by device type.",
"tags": [
"Stats"
],
"x-stoplight": {
"id": "GET_devices-stats",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/geo/stats": {
"get": {
"description": "**This endpoint allows you to retrieve your email statistics segmented by country and state/province.**\n\n**We only store up to 7 days of email activity in our database.** By default, 500 items will be returned per request via the Advanced Stats API endpoints.\n\nAdvanced Stats provide a more in-depth view of your email statistics and the actions taken by your recipients. You can segment these statistics by geographic location, device type, client type, browser, and mailbox provider. For more information about statistics, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Statistics/index.html).",
"operationId": "GET_geo-stats",
"parameters": [
{
"description": "The country you would like to see statistics for. Currently only supported for US and CA.",
"in": "query",
"name": "country",
"schema": {
"enum": [
"US",
"CA"
],
"type": "string"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
},
{
"$ref": "#/components/parameters/trait_statsAdvancedQueryStringsLimitOffset_limit"
},
{
"$ref": "#/components/parameters/trait_statsAdvancedQueryStringsLimitOffset_offset"
},
{
"$ref": "#/components/parameters/trait_statsAdvancedQueryStringsLimitOffset_aggregated_by"
},
{
"$ref": "#/components/parameters/trait_statsAdvancedQueryStringsLimitOffset_start_date"
},
{
"$ref": "#/components/parameters/trait_statsAdvancedQueryStringsLimitOffset_end_date"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"date": "2015-10-11",
"stats": [
{
"metrics": {
"clicks": 0,
"opens": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "TX",
"type": "province"
}
]
},
{
"date": "2015-10-12",
"stats": [
{
"metrics": {
"clicks": 0,
"opens": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "TX",
"type": "province"
}
]
},
{
"date": "2015-10-13",
"stats": [
{
"metrics": {
"clicks": 0,
"opens": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "TX",
"type": "province"
}
]
},
{
"date": "2015-10-14",
"stats": [
{
"metrics": {
"clicks": 0,
"opens": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "TX",
"type": "province"
}
]
},
{
"date": "2015-10-15",
"stats": [
{
"metrics": {
"clicks": 0,
"opens": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "TX",
"type": "province"
}
]
},
{
"date": "2015-10-16",
"stats": [
{
"metrics": {
"clicks": 0,
"opens": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "TX",
"type": "province"
}
]
},
{
"date": "2015-10-17",
"stats": [
{
"metrics": {
"clicks": 0,
"opens": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "TX",
"type": "province"
}
]
},
{
"date": "2015-10-18",
"stats": [
{
"metrics": {
"clicks": 0,
"opens": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "TX",
"type": "province"
}
]
},
{
"date": "2015-10-19",
"stats": [
{
"metrics": {
"clicks": 0,
"opens": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "TX",
"type": "province"
}
]
},
{
"date": "2015-10-20",
"stats": [
{
"metrics": {
"clicks": 0,
"opens": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "TX",
"type": "province"
}
]
},
{
"date": "2015-10-21",
"stats": [
{
"metrics": {
"clicks": 0,
"opens": 1,
"unique_clicks": 0,
"unique_opens": 1
},
"name": "TX",
"type": "province"
}
]
},
{
"date": "2015-10-22",
"stats": [
{
"metrics": {
"clicks": 0,
"opens": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "TX",
"type": "province"
}
]
},
{
"date": "2015-10-23",
"stats": [
{
"metrics": {
"clicks": 0,
"opens": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "TX",
"type": "province"
}
]
},
{
"date": "2015-10-24",
"stats": [
{
"metrics": {
"clicks": 0,
"opens": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "TX",
"type": "province"
}
]
},
{
"date": "2015-10-25",
"stats": [
{
"metrics": {
"clicks": 0,
"opens": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "TX",
"type": "province"
}
]
},
{
"date": "2015-10-26",
"stats": [
{
"metrics": {
"clicks": 0,
"opens": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "TX",
"type": "province"
}
]
},
{
"date": "2015-10-27",
"stats": [
{
"metrics": {
"clicks": 0,
"opens": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "TX",
"type": "province"
}
]
},
{
"date": "2015-10-28",
"stats": [
{
"metrics": {
"clicks": 0,
"opens": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "TX",
"type": "province"
}
]
},
{
"date": "2015-10-29",
"stats": [
{
"metrics": {
"clicks": 0,
"opens": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "TX",
"type": "province"
}
]
},
{
"date": "2015-10-30",
"stats": [
{
"metrics": {
"clicks": 0,
"opens": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "TX",
"type": "province"
}
]
},
{
"date": "2015-10-31",
"stats": [
{
"metrics": {
"clicks": 0,
"opens": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "TX",
"type": "province"
}
]
},
{
"date": "2015-11-01",
"stats": [
{
"metrics": {
"clicks": 0,
"opens": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "TX",
"type": "province"
}
]
},
{
"date": "2015-11-02",
"stats": [
{
"metrics": {
"clicks": 0,
"opens": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "TX",
"type": "province"
}
]
},
{
"date": "2015-11-03",
"stats": [
{
"metrics": {
"clicks": 0,
"opens": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "TX",
"type": "province"
}
]
},
{
"date": "2015-11-04",
"stats": [
{
"metrics": {
"clicks": 0,
"opens": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "TX",
"type": "province"
}
]
},
{
"date": "2015-11-05",
"stats": [
{
"metrics": {
"clicks": 0,
"opens": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "TX",
"type": "province"
}
]
},
{
"date": "2015-11-06",
"stats": [
{
"metrics": {
"clicks": 0,
"opens": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "TX",
"type": "province"
}
]
},
{
"date": "2015-11-07",
"stats": [
{
"metrics": {
"clicks": 0,
"opens": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "TX",
"type": "province"
}
]
},
{
"date": "2015-11-08",
"stats": [
{
"metrics": {
"clicks": 0,
"opens": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "TX",
"type": "province"
}
]
},
{
"date": "2015-11-09",
"stats": [
{
"metrics": {
"clicks": 0,
"opens": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "TX",
"type": "province"
}
]
},
{
"date": "2015-11-10",
"stats": [
{
"metrics": {
"clicks": 0,
"opens": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "TX",
"type": "province"
}
]
}
]
}
},
"schema": {
"items": {
"properties": {
"date": {
"description": "The date that the statistics were gathered.",
"type": "string"
},
"stats": {
"description": "The list of statistics.",
"items": {
"properties": {
"metrics": {
"$ref": "#/components/schemas/advanced_stats_clicks_opens"
},
"name": {
"description": "The name of the specific segmentation.",
"type": "string"
},
"type": {
"description": "The type of segmentation.",
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve email statistics by country and state/province.",
"tags": [
"Stats"
],
"x-stoplight": {
"id": "GET_geo-stats",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/ips": {
"get": {
"description": "**This endpoint allows you to retrieve a list of all assigned and unassigned IPs.**\n\nResponse includes warm up status, pools, assigned subusers, and reverse DNS info. The start_date field corresponds to when warmup started for that IP.\n\nA single IP address or a range of IP addresses may be dedicated to an account in order to send email for multiple domains. The reputation of this IP is based on the aggregate performance of all the senders who use it.",
"operationId": "GET_ips",
"parameters": [
{
"description": "The IP address to get",
"in": "query",
"name": "ip",
"schema": {
"type": "string"
}
},
{
"description": "Should we exclude reverse DNS records (whitelabels)?",
"in": "query",
"name": "exclude_whitelabels",
"schema": {
"type": "boolean"
}
},
{
"description": "The number of IPs you want returned at the same time.",
"in": "query",
"name": "limit",
"schema": {
"default": 10,
"type": "integer"
}
},
{
"description": "The offset for the number of IPs that you are requesting.",
"in": "query",
"name": "offset",
"schema": {
"default": 0,
"type": "integer"
}
},
{
"description": "The subuser you are requesting for.",
"in": "query",
"name": "subuser",
"schema": {
"type": "string"
}
},
{
"description": "The direction to sort the results.",
"in": "query",
"name": "sort_by_direction",
"schema": {
"enum": [
"desc",
"asc"
],
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"assigned_at": 1482883200,
"ip": "192.168.1.1",
"pools": [
"pool1",
"pool2"
],
"start_date": 1409616000,
"subusers": [
"tim@sendgrid.net"
],
"warmup": false,
"whitelabeled": false
},
{
"assigned_at": 1482883200,
"ip": "208.115.214.22",
"pools": [],
"rdns": "o1.email.burgermail.com",
"start_date": 1409616000,
"subusers": [],
"warmup": false,
"whitelabeled": true
}
]
}
},
"schema": {
"items": {
"properties": {
"assigned_at": {
"description": "The date that the IP address was assigned to the user.",
"nullable": true,
"type": "integer"
},
"ip": {
"description": "An IP address.",
"type": "string"
},
"pools": {
"description": "The IP pools that this IP has been added to.",
"items": {
"type": "string"
},
"type": "array"
},
"rdns": {
"description": "The reverse DNS record for this IP address.",
"type": "string"
},
"start_date": {
"description": "The date that the IP address was entered into warmup.",
"nullable": true,
"type": "number"
},
"subusers": {
"description": "The subusers that are able to send email from this IP.",
"items": {
"type": "string"
},
"type": "array"
},
"warmup": {
"description": "Indicates if this IP address is currently warming up.",
"type": "boolean"
},
"whitelabeled": {
"description": "Indicates if this IP address is associated with a reverse DNS record.",
"type": "boolean"
}
},
"required": [
"ip",
"subusers",
"pools",
"warmup",
"start_date",
"whitelabeled",
"assigned_at"
],
"type": "object"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve all IP addresses",
"tags": [
"IP Addresses"
],
"x-stoplight": {
"id": "GET_ips",
"mock": {
"dynamic": false
},
"public": true
}
},
"post": {
"description": "**This endpoint is for adding a(n) IP Address(es) to your account.**",
"operationId": "POST_ips",
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"count": 90323478,
"subusers": [
"subuser1",
"subuser2"
],
"user_can_send": true,
"warmup": true
},
"properties": {
"count": {
"description": "The amount of IPs to add to the account.",
"type": "integer"
},
"subusers": {
"description": "Array of usernames to be assigned a send IP.",
"items": {
"type": "string"
},
"type": "array"
},
"warmup": {
"default": false,
"description": "Whether or not to warmup the IPs being added.",
"type": "boolean"
}
},
"required": [
"count"
],
"type": "object"
}
}
}
},
"responses": {
"201": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"ips": [
{
"ip": "1.2.3.4",
"subusers": [
"user",
"subuser1"
]
},
{
"ip": "1.2.3.5",
"subusers": [
"user",
"subuser1"
]
}
],
"remaining_ips": 1,
"warmup": true
}
}
},
"schema": {
"properties": {
"ips": {
"description": "List of IP objects.",
"items": {
"properties": {
"ip": {
"description": "IP added to account.",
"type": "string"
},
"subusers": {
"description": "Array of usernames assigned a send IP.",
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"ip",
"subusers"
],
"type": "object"
},
"type": "array"
},
"remaining_ips": {
"description": "The number of IPs that can still be added to the user.",
"type": "integer"
},
"warmup": {
"description": "Whether or not the IPs are being warmed up.",
"type": "boolean"
}
},
"required": [
"ips",
"remaining_ips",
"warmup"
],
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "one or more subusers do not belong to this user"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Add IPs",
"tags": [
"IP Addresses"
],
"x-stoplight": {
"id": "POST_ips",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/ips/assigned": {
"get": {
"description": "**This endpoint allows you to retrieve only assigned IP addresses.**\n\nA single IP address or a range of IP addresses may be dedicated to an account in order to send email for multiple domains. The reputation of this IP is based on the aggregate performance of all the senders who use it.",
"operationId": "GET_ips-assigned",
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"ip": "167.89.21.3",
"pools": [
"new_test5"
],
"start_date": 1409616000,
"warmup": true
}
]
}
},
"schema": {
"items": {
"properties": {
"ip": {
"description": "The IP address.",
"type": "string"
},
"pools": {
"description": "The IP pools that this IP address has been added to.",
"items": {
"type": "string"
},
"type": "array"
},
"start_date": {
"description": "The start date that this IP address was entered into warmup.",
"type": "integer"
},
"warmup": {
"description": "Indicates if this IP address is currently warming up.",
"type": "boolean"
}
},
"required": [
"ip",
"pools",
"warmup",
"start_date"
],
"type": "object"
},
"title": "List all assigned IPs response",
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve all assigned IPs",
"tags": [
"IP Addresses"
],
"x-stoplight": {
"id": "GET_ips-assigned",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/ips/pools": {
"get": {
"description": "**This endpoint allows you to get all of your IP pools.**",
"operationId": "GET_ips-pools",
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"name": "marketing"
},
{
"name": "transactional"
}
]
}
},
"schema": {
"items": {
"$ref": "#/components/schemas/ip_pool_response"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve all IP pools",
"tags": [
"IP Pools"
],
"x-stoplight": {
"id": "GET_ips-pools",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"post": {
"description": "**This endpoint allows you to create an IP pool.**\n\nBefore you can create an IP pool, you need to activate the IP in your SendGrid account: \n\n1. Log into your SendGrid account. \n1. Navigate to **Settings** and then select **IP Addresses**. \n1. Find the IP address you want to activate and then click **Edit**. \n1. Check **Allow my account to send mail using this IP address**.\n1. Click **Save**.",
"operationId": "POST_ips-pools",
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"name": "marketing"
},
"properties": {
"name": {
"description": "The name of your new IP pool.",
"maxLength": 64,
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"name": "marketing"
}
}
},
"schema": {
"$ref": "#/components/schemas/ip_pool_response"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Create an IP pool",
"tags": [
"IP Pools"
],
"x-stoplight": {
"id": "POST_ips-pools",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/ips/pools/{pool_name}": {
"delete": {
"description": "**This endpoint allows you to delete an IP pool.**",
"operationId": "DELETE_ips-pools-pool_name",
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"error": "Unable to locate specified IPs Pool"
}
}
},
"schema": {
"properties": {
"error": {
"description": "An error explaining why the pool could not be deleted.",
"type": "string"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete an IP pool",
"tags": [
"IP Pools"
],
"x-stoplight": {
"id": "DELETE_ips-pools-poolname",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to get all of the IP addresses that are in a specific IP pool.**",
"operationId": "GET_ips-pools-pool_name",
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"ips": [
"192.168.1.1",
"192.158.1.2",
"192.138.2.1"
],
"pool_name": "Sample"
}
}
},
"schema": {
"properties": {
"ips": {
"description": "The IP addresses that belong to this pool.",
"items": {
"type": "string"
},
"type": "array"
},
"pool_name": {
"description": "The name of the IP pool.",
"maxLength": 64,
"type": "string"
}
},
"type": "object"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": "error",
"message": "Unable to locate specified IPs Pool"
}
]
}
}
},
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"field": {
"description": "The name of the error.",
"type": "string"
},
"message": {
"description": "A message explaining why the IP addresses could not be listed.",
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve all the IPs in a specified pool",
"tags": [
"IP Pools"
],
"x-stoplight": {
"id": "GET_ips-pools-poolname",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"description": "The name of the IP pool that you want to retrieve the IP addresses for.",
"in": "path",
"name": "pool_name",
"required": true,
"schema": {
"type": "string"
}
}
],
"put": {
"description": "**This endpoint allows you to update the name of an IP pool.**",
"operationId": "PUT_ips-pools-pool_name",
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"name": "new_pool_name"
},
"properties": {
"name": {
"description": "The new name for your IP pool.",
"maxLength": 64,
"type": "string"
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"name": "new_pool_name"
}
}
},
"schema": {
"$ref": "#/components/schemas/ip_pool_response"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "ip pool not found"
}
]
}
}
},
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"field": {
"nullable": true,
"type": "string"
},
"message": {
"description": "A message explaining why the name of your IP pool could not be updated.",
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Rename an IP pool",
"tags": [
"IP Pools"
],
"x-stoplight": {
"id": "PUT_ips-pools-poolname",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/ips/pools/{pool_name}/ips": {
"parameters": [
{
"description": "The name of the IP pool you want to add the address to. If the name contains spaces, they must be URL encoded (e.g., \"Test Pool\" becomes \"Test%20Pool\").",
"in": "path",
"name": "pool_name",
"required": true,
"schema": {
"type": "string"
}
}
],
"post": {
"description": "**This endpoint allows you to add an IP address to an IP pool.**\n\nYou can add the same IP address to multiple pools. It may take up to 60 seconds for your IP address to be added to a pool after your request is made.\n\nBefore you can add an IP to a pool, you need to activate it in your SendGrid account:\n\n1. Log into your SendGrid account. \n1. Navigate to **Settings** and then select **IP Addresses**. \n1. Find the IP address you want to activate and then click **Edit**. \n1. Check **Allow my account to send mail using this IP address**.\n1. Click **Save**.\n\nYou can retrieve all of your available IP addresses from the \"Retrieve all IP addresses\" endpoint.",
"operationId": "POST_ips-pools-pool_name-ips",
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"ip": "0.0.0.0"
},
"properties": {
"ip": {
"description": "The IP address that you want to add to the named pool.",
"type": "string"
}
},
"type": "object"
}
}
}
},
"responses": {
"201": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"ip": "000.00.00.0",
"pools": [
"test1"
],
"start_date": 1409616000,
"warmup": true
}
}
},
"schema": {
"properties": {
"ip": {
"description": "The IP address.",
"type": "string"
},
"pools": {
"description": "The IP pools that this IP address has been added to.",
"items": {
"type": "string"
},
"type": "array"
},
"start_date": {
"description": "A Unix timestamp indicating when the warmup process began for the added IP address.",
"type": "integer"
},
"warmup": {
"description": "Indicates if the IP address is in warmup.",
"type": "boolean"
}
},
"required": [
"ip",
"pools",
"start_date",
"warmup"
],
"type": "object"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "resource not found"
}
]
}
}
},
"schema": {
"properties": {
"errors": {
"description": "The error returned.",
"items": {
"properties": {
"field": {
"nullable": true,
"type": "string"
},
"message": {
"description": "A message explaining why the IP address could not be added to the IP Pool.",
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Add an IP address to a pool",
"tags": [
"IP Pools"
],
"x-stoplight": {
"id": "POST_ips-pools-poolname-ips",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/ips/pools/{pool_name}/ips/{ip}": {
"delete": {
"description": "**This endpoint allows you to remove an IP address from an IP pool.**",
"operationId": "DELETE_ips-pools-pool_name-ips-ip",
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"error": "Unable to locate specified IPs Pool"
}
}
},
"schema": {
"properties": {
"error": {
"description": "An error explaining why the IP address could not be removed from the IP pool.",
"type": "string"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Remove an IP address from a pool",
"tags": [
"IP Pools"
],
"x-stoplight": {
"id": "DELETE_ips-pools-poolname-ips-ip",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"description": "The name of the IP pool that you are removing the IP address from.",
"in": "path",
"name": "pool_name",
"required": true,
"schema": {
"type": "string"
}
},
{
"description": "The IP address that you wish to remove.",
"in": "path",
"name": "ip",
"required": true,
"schema": {
"type": "string"
}
}
]
},
"/ips/remaining": {
"get": {
"description": "**This endpoint gets amount of IP Addresses that can still be created during a given period and the price of those IPs.**",
"operationId": "GET_ips-remaining",
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"results": [
{
"period": "month",
"price_per_ip": 20,
"remaining": 2
}
]
}
}
},
"schema": {
"properties": {
"results": {
"items": {
"properties": {
"period": {
"description": "The length of time until user can add more IPs.",
"type": "string"
},
"price_per_ip": {
"description": "The current cost to add an IP.",
"type": "number"
},
"remaining": {
"description": "The number of IPs that can still be added to the user.",
"type": "integer"
}
},
"required": [
"remaining",
"period",
"price_per_ip"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"results"
],
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Get remaining IPs count",
"tags": [
"IP Addresses"
],
"x-stoplight": {
"id": "GET_ips-remaining",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/ips/warmup": {
"get": {
"description": "**This endpoint allows you to retrieve all of your IP addresses that are currently warming up.**",
"operationId": "GET_ips-warmup",
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"ip": "0.0.0.0",
"start_date": 1409616000
}
]
}
},
"schema": {
"$ref": "#/components/schemas/ip_warmup_response"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve all IPs currently in warmup",
"tags": [
"IP Warmup"
],
"x-stoplight": {
"id": "GET_ips-warmup",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"post": {
"description": "**This endpoint allows you to put an IP address into warmup mode.**",
"operationId": "POST_ips-warmup",
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"ip": "0.0.0.0"
},
"properties": {
"ip": {
"description": "The IP address that you want to begin warming up.",
"type": "string"
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"ip": "0.0.0.0",
"start_date": 1409616000
}
]
}
},
"schema": {
"$ref": "#/components/schemas/ip_warmup_response"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "resource not found"
}
]
}
}
},
"schema": {
"properties": {
"errors": {
"description": "The errors that were encountered.",
"items": {
"properties": {
"field": {
"nullable": true,
"type": "string"
},
"message": {
"description": "A message explaining why the IP couldn't entered into warmup mode.",
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Start warming up an IP address",
"tags": [
"IP Warmup"
],
"x-stoplight": {
"id": "POST_ips-warmup",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/ips/warmup/{ip_address}": {
"delete": {
"description": "**This endpoint allows you to remove an IP address from warmup mode.**\n\nYour request will return a 204 status code if the specified IP was successfully removed from warmup mode. To retrieve details of the IP’s warmup status *before* removing it from warmup mode, call the \"Retrieve the warmpup status for a specific IP address\" endpoint.",
"operationId": "DELETE_ips-warmup-ip_address",
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "resource not found"
}
]
}
}
},
"schema": {
"properties": {
"errors": {
"description": "The errors that were encountered.",
"items": {
"properties": {
"field": {
"nullable": true,
"type": "string"
},
"message": {
"description": "A message explaining why the IP couldn't be removed from warmup.",
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Stop warming up an IP address",
"tags": [
"IP Warmup"
],
"x-stoplight": {
"id": "DELETE_ips-warmup-ipaddress",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve the warmup status for a specific IP address.**\n\nYou can retrieve all of your warming IPs using the \"Retrieve all IPs currently in warmup\" endpoint.",
"operationId": "GET_ips-warmup-ip_address",
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"ip": "0.0.0.0",
"start_date": 1409616000
}
]
}
},
"schema": {
"$ref": "#/components/schemas/ip_warmup_response"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "resource not found"
}
]
}
}
},
"schema": {
"properties": {
"errors": {
"description": "The errors that were encountered.",
"items": {
"properties": {
"field": {
"nullable": true,
"type": "string"
},
"message": {
"description": "A message explaining why the warmup status could not be retrieved.",
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve the warmup status for a specific IP address",
"tags": [
"IP Warmup"
],
"x-stoplight": {
"id": "GET_ips-warmup-ipaddress",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"description": "The IP address that you want to retrieve the warmup status for.",
"in": "path",
"name": "ip_address",
"required": true,
"schema": {
"type": "string"
}
}
]
},
"/ips/{ip_address}": {
"get": {
"description": "**This endpoint allows you to see which IP pools a particular IP address has been added to.**\n\nThe same IP address can be added to multiple IP pools.\n\nA single IP address or a range of IP addresses may be dedicated to an account in order to send email for multiple domains. The reputation of this IP is based on the aggregate performance of all the senders who use it.",
"operationId": "GET_ips-ip_address",
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"ip": "000.00.00.0",
"pools": [
"test1"
],
"rdns": "o1.em.example.com",
"start_date": null,
"subusers": [
"subuser1",
"subuser2"
],
"warmup": false,
"whitelabeled": true
}
}
},
"schema": {
"properties": {
"ip": {
"description": "The IP address.",
"type": "string"
},
"pools": {
"description": "The list of IP pools that this IP address belongs to.",
"items": {
"type": "string"
},
"type": "array"
},
"rdns": {
"description": "The reverse DNS record for this IP address.",
"type": "string"
},
"start_date": {
"description": "The date that the IP address was entered into warmup.",
"nullable": true,
"type": "integer"
},
"subusers": {
"description": "The subusers that can send email using this IP address.",
"items": {
"type": "string"
},
"type": "array"
},
"warmup": {
"description": "Indicates if this IP address is currently warming up.",
"type": "boolean"
},
"whitelabeled": {
"description": "Indicates if this IP address is associated with a reverse DNS record.",
"type": "boolean"
}
},
"required": [
"ip",
"subusers",
"rdns",
"pools",
"warmup",
"start_date",
"whitelabeled"
],
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve all IP pools an IP address belongs to",
"tags": [
"IP Addresses"
],
"x-stoplight": {
"id": "GET_ips-ipaddress",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"description": "The IP address you are retrieving the IP pools for.",
"in": "path",
"name": "ip_address",
"required": true,
"schema": {
"type": "string"
}
}
]
},
"/mail/batch": {
"post": {
"description": "**This endpoint allows you to generate a new batch ID.**\n\nOnce a `batch_id` is created, you can associate it with a scheduled send using the `/mail/send` endpoint. Passing the `batch_id` as a field in the `/mail/send` request body will assign the ID to the send you are creating.\n\nOnce an ID is associated with a scheduled send, the send can be accessed and its send status can be modified using the `batch_id`.",
"operationId": "POST_mail-batch",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"201": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"batch_id": "HkJ5yLYULb7Rj8GKSx7u025ouWVlMgAi"
}
}
},
"schema": {
"$ref": "#/components/schemas/mail_batch_id"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_cancelScheduledSendsErrors_400"
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Create a batch ID",
"tags": [
"Cancel Scheduled Sends"
],
"x-stoplight": {
"id": "POST_mail-batch",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/mail/batch/{batch_id}": {
"get": {
"description": "**This endpoint allows you to validate a batch ID.**\n\nWhen you pass a valid `batch_id` to this endpoint, it will return a `200` status code and the batch ID itself.\n\nIf you pass an invalid `batch_id` to the endpoint, you will receive a `400` level status code and an error message.\n\nA `batch_id` does not need to be assigned to a scheduled send to be considered valid. A successful response means only that the `batch_id` has been created, but it does not indicate that it has been associated with a send.",
"operationId": "GET_mail-batch-batch_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"batch_id": "HkJ5yLYULb7Rj8GKSx7u025ouWVlMgAi"
}
}
},
"schema": {
"$ref": "#/components/schemas/mail_batch_id"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_cancelScheduledSendsErrors_400"
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Validate batch ID",
"tags": [
"Cancel Scheduled Sends"
],
"x-stoplight": {
"id": "GET_mail-batch-batchid",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"in": "path",
"name": "batch_id",
"required": true,
"schema": {
"type": "string"
}
}
]
},
"/mail/send": {
"post": {
"description": "The Mail Send endpoint allows you to send email over SendGrid’s v3 Web API, the most recent version of our API. If you are looking for documentation about the v2 Mail Send endpoint, see our [v2 API Reference](https://sendgrid.com/docs/API_Reference/Web_API/mail.html).\n\n## Helper Libraries\n\nTwilio SendGrid provides libraries to help you quickly and easily integrate with the v3 Web API in 7 different languages:\n\n* [C#](https://github.com/sendgrid/sendgrid-csharp) \n* [Go](https://github.com/sendgrid/sendgrid-go)\n* [Java](https://github.com/sendgrid/sendgrid-java)\n* [Node JS](https://github.com/sendgrid/sendgrid-nodejs)\n* [PHP](https://github.com/sendgrid/sendgrid-php)\n* [Python](https://github.com/sendgrid/sendgrid-python)\n* [Ruby](https://github.com/sendgrid/sendgrid-ruby)\n\n## Dynamic Transactional Templates and Handlebars\n\nIn order to send a dynamic template, specify the template ID with the `template_id` parameter. \n\nTo specify handlebar substitutions, define your substitutions in the request JSON with this syntax:\n\n```\n\"dynamic_template_data\": {\n \"guest\": \"Jane Doe\",\n \"partysize\": \"4\",\n \"english\": true,\n \"date\": \"April 1st, 2021\"\n }\n```\n\nFor more information about Dynamic Transactional Templates and Handlebars, see our documentation and reference pages.\n\n* [How to send an email with Dynamic Transactional Templates\n](https://sendgrid.com/docs/ui/sending-email/how-to-send-an-email-with-dynamic-transactional-templates/)\n* [Using Handlebars](https://sendgrid.com/docs/for-developers/sending-email/using-handlebars/) \n\n## Mail Body Compression\n\nMail body compression is available to some high volume accounts. Talk to your CSM if you are interested in this functionality. Mail body compression works by setting up a JSON payload as defined on this page, then compressing it with gzip (the gzip file can be no more than 30mb).\n\nTo use mail body compression:\n\n1. Add a `Content-Encoding` header, with a value of `gzip`. \n a. `Content-Encoding: gzip` \n2. Send the gzip as a data-binary. \n a. `--data-binary '@data.json.gz'\n`\n\n## Multiple Reply-To Emails\n\nUsing `reply_to_list` allows senders to include more than one recipient email address to receive reply and/or bounce messages from the recipient of the email.\n\n### Usage Considerations\n\n* `reply_to` is mutually exclusive with `reply_to_list`. If both are used, then the API call will be rejected. \n* The `reply_to_list` object, when used, must at least have an email parameter and may also contain a name parameter.\n* Each email address in the `reply_to_list` should be unique.\n* There is a limit of 1000 `reply_to_list` emails per mail/send request.\n* In SMTP calls, we will omit any invalid emails.\n\n### Possible 400 Error Messages\n\n* `reply_to` is mutually exclusive with `reply_to_list`.\n* The `reply_to_list` object, when used, must at least have an email parameter and may also contain a name parameter.\n* Each email address in the `reply_to_list` should be unique.\n* There is a limit of X `reply_to` emails per mail/send request.\n* The `reply_to_list` email does not contain a valid address.\n* The `reply_to_list` email exceeds the maximum total length of X characters.\n* The `reply_to_list` email parameter is required.",
"operationId": "POST_mail-send",
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"asm": {
"group_id": 12345,
"groups_to_display": [
12345
]
},
"attachments": [
{
"content": "PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ImVuIj4KCiAgICA8aGVhZD4KICAgICAgICA8bWV0YSBjaGFyc2V0PSJVVEYtOCI+CiAgICAgICAgPG1ldGEgaHR0cC1lcXVpdj0iWC1VQS1Db21wYXRpYmxlIiBjb250ZW50PSJJRT1lZGdlIj4KICAgICAgICA8bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLCBpbml0aWFsLXNjYWxlPTEuMCI+CiAgICAgICAgPHRpdGxlPkRvY3VtZW50PC90aXRsZT4KICAgIDwvaGVhZD4KCiAgICA8Ym9keT4KCiAgICA8L2JvZHk+Cgo8L2h0bWw+Cg==",
"disposition": "attachment",
"filename": "index.html",
"type": "text/html"
}
],
"batch_id": "AsdFgHjklQweRTYuIopzXcVBNm0aSDfGHjklmZcVbNMqWert1znmOP2asDFjkl",
"categories": [
"cake",
"pie",
"baking"
],
"content": [
{
"type": "text/html",
"value": "Hello from Twilio SendGrid!
Sending with the email service trusted by developers and marketers for time-savings , scalability , and delivery expertise .
%open-track%
"
}
],
"from": {
"email": "orders@example.com",
"name": "Example Order Confirmation"
},
"ip_pool_name": "transactional email",
"mail_settings": {
"bypass_list_management": {
"enable": false
},
"footer": {
"enable": false
},
"sandbox_mode": {
"enable": false
}
},
"personalizations": [
{
"bcc": [
{
"email": "james_doe@example.com",
"name": "Jim Doe"
}
],
"cc": [
{
"email": "jane_doe@example.com",
"name": "Jane Doe"
}
],
"to": [
{
"email": "john_doe@example.com",
"name": "John Doe"
},
{
"email": "julia_doe@example.com",
"name": "Julia Doe"
}
]
},
{
"bcc": [
{
"email": "jordan_doe@example.com",
"name": "Jordan Doe"
}
],
"from": {
"email": "sales@example.com",
"name": "Example Sales Team"
},
"to": [
{
"email": "janice_doe@example.com",
"name": "Janice Doe"
}
]
}
],
"reply_to": {
"email": "customer_service@example.com",
"name": "Example Customer Service Team"
},
"send_at": 1617260400,
"subject": "Your Example Order Confirmation",
"tracking_settings": {
"click_tracking": {
"enable": true,
"enable_text": false
},
"open_tracking": {
"enable": true,
"substitution_tag": "%open-track%"
},
"subscription_tracking": {
"enable": false
}
}
},
"properties": {
"asm": {
"description": "An object allowing you to specify how to handle unsubscribes.",
"properties": {
"group_id": {
"description": "The unsubscribe group to associate with this email.",
"type": "integer"
},
"groups_to_display": {
"description": "An array containing the unsubscribe groups that you would like to be displayed on the unsubscribe preferences page.",
"items": {
"type": "integer"
},
"maxItems": 25,
"type": "array"
}
},
"required": [
"group_id"
],
"type": "object"
},
"attachments": {
"description": "An array of objects where you can specify any attachments you want to include.",
"items": {
"properties": {
"content": {
"description": "The Base64 encoded content of the attachment.",
"minLength": 1,
"type": "string"
},
"content_id": {
"description": "The attachment's content ID. This is used when the disposition is set to `“inline”` and the attachment is an image, allowing the file to be displayed within the body of your email.",
"type": "string"
},
"disposition": {
"default": "attachment",
"description": "The attachment's content-disposition, specifying how you would like the attachment to be displayed. For example, `“inline”` results in the attached file are displayed automatically within the message while `“attachment”` results in the attached file require some action to be taken before it is displayed, such as opening or downloading the file.",
"enum": [
"inline",
"attachment"
],
"type": "string"
},
"filename": {
"description": "The attachment's filename.",
"type": "string"
},
"type": {
"description": "The MIME type of the content you are attaching (e.g., `“text/plain”` or `“text/html”`).",
"minLength": 1,
"type": "string"
}
},
"required": [
"content",
"filename"
],
"type": "object"
},
"type": "array"
},
"batch_id": {
"description": "An ID representing a batch of emails to be sent at the same time. Including a `batch_id` in your request allows you include this email in that batch. It also enables you to cancel or pause the delivery of that batch. For more information, see the [Cancel Scheduled Sends API](https://sendgrid.com/docs/api-reference/).",
"type": "string"
},
"categories": {
"description": "An array of category names for this message. Each category name may not exceed 255 characters. ",
"items": {
"maxLength": 255,
"type": "string"
},
"maxItems": 10,
"type": "array",
"uniqueItems": true
},
"content": {
"description": "An array where you can specify the content of your email. You can include multiple [MIME types](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types) of content, but you must specify at least one MIME type. To include more than one MIME type, add another object to the array containing the `type` and `value` parameters.",
"items": {
"properties": {
"type": {
"description": "The MIME type of the content you are including in your email (e.g., `“text/plain”` or `“text/html”`).",
"minLength": 1,
"type": "string"
},
"value": {
"description": "The actual content of the specified MIME type that you are including in your email.",
"minLength": 1,
"type": "string"
}
},
"required": [
"type",
"value"
],
"type": "object"
},
"type": "array"
},
"custom_args": {
"description": "Values that are specific to the entire send that will be carried along with the email and its activity data. Key/value pairs must be strings. Substitutions will not be made on custom arguments, so any string that is entered into this parameter will be assumed to be the custom argument that you would like to be used. This parameter is overridden by `custom_args` set at the personalizations level. Total `custom_args` size may not exceed 10,000 bytes.",
"type": "string"
},
"from": {
"$ref": "#/components/schemas/from_email_object"
},
"headers": {
"description": "An object containing key/value pairs of header names and the value to substitute for them. The key/value pairs must be strings. You must ensure these are properly encoded if they contain unicode characters. These headers cannot be one of the reserved headers.",
"type": "object"
},
"ip_pool_name": {
"description": "The IP Pool that you would like to send this email from.",
"maxLength": 64,
"minLength": 2,
"type": "string"
},
"mail_settings": {
"description": "A collection of different mail settings that you can use to specify how you would like this email to be handled.",
"properties": {
"bypass_bounce_management": {
"description": "Allows you to bypass the bounce list to ensure that the email is delivered to recipients. Spam report and unsubscribe lists will still be checked; addresses on these other lists will not receive the message. This filter cannot be combined with the `bypass_list_management` filter. See our [documentation](https://sendgrid.com/docs/ui/sending-email/index-suppressions/#bypass-suppressions) for more about bypass filters.",
"properties": {
"enable": {
"description": "Indicates if this setting is enabled.",
"type": "boolean"
}
},
"type": "object"
},
"bypass_list_management": {
"description": "Allows you to bypass all unsubscribe groups and suppressions to ensure that the email is delivered to every single recipient. This should only be used in emergencies when it is absolutely necessary that every recipient receives your email. This filter cannot be combined with any other bypass filters. See our [documentation](https://sendgrid.com/docs/ui/sending-email/index-suppressions/#bypass-suppressions) for more about bypass filters.",
"properties": {
"enable": {
"description": "Indicates if this setting is enabled.",
"type": "boolean"
}
},
"type": "object"
},
"bypass_spam_management": {
"description": "Allows you to bypass the spam report list to ensure that the email is delivered to recipients. Bounce and unsubscribe lists will still be checked; addresses on these other lists will not receive the message. This filter cannot be combined with the `bypass_list_management` filter. See our [documentation](https://sendgrid.com/docs/ui/sending-email/index-suppressions/#bypass-suppressions) for more about bypass filters.",
"properties": {
"enable": {
"description": "Indicates if this setting is enabled.",
"type": "boolean"
}
},
"type": "object"
},
"bypass_unsubscribe_management": {
"description": "Allows you to bypass the global unsubscribe list to ensure that the email is delivered to recipients. Bounce and spam report lists will still be checked; addresses on these other lists will not receive the message. This filter applies only to global unsubscribes and will not bypass group unsubscribes. This filter cannot be combined with the `bypass_list_management` filter. See our [documentation](https://sendgrid.com/docs/ui/sending-email/index-suppressions/#bypass-suppressions) for more about bypass filters.",
"properties": {
"enable": {
"description": "Indicates if this setting is enabled.",
"type": "boolean"
}
},
"type": "object"
},
"footer": {
"description": "The default footer that you would like included on every email.",
"properties": {
"enable": {
"description": "Indicates if this setting is enabled.",
"type": "boolean"
},
"html": {
"description": "The HTML content of your footer.",
"type": "string"
},
"text": {
"description": "The plain text content of your footer.",
"type": "string"
}
},
"type": "object"
},
"sandbox_mode": {
"description": "Sandbox Mode allows you to send a test email to ensure that your request body is valid and formatted correctly.",
"properties": {
"enable": {
"description": "Indicates if this setting is enabled.",
"type": "boolean"
}
},
"type": "object"
}
},
"type": "object"
},
"personalizations": {
"description": "An array of messages and their metadata. Each object within personalizations can be thought of as an envelope - it defines who should receive an individual message and how that message should be handled. See our [Personalizations documentation](https://sendgrid.com/docs/for-developers/sending-email/personalizations/) for examples.",
"items": {
"properties": {
"bcc": {
"description": "An array of recipients who will receive a blind carbon copy of your email. Each object in this array must contain the recipient's email address. Each object in the array may optionally contain the recipient's name.",
"items": {
"$ref": "#/components/schemas/cc_bcc_email_object"
},
"maxItems": 1000,
"type": "array"
},
"cc": {
"description": "An array of recipients who will receive a copy of your email. Each object in this array must contain the recipient's email address. Each object in the array may optionally contain the recipient's name.",
"items": {
"$ref": "#/components/schemas/cc_bcc_email_object"
},
"maxItems": 1000,
"type": "array"
},
"custom_args": {
"description": "Values that are specific to this personalization that will be carried along with the email and its activity data. Substitutions will not be made on custom arguments, so any string that is entered into this parameter will be assumed to be the custom argument that you would like to be used. This field may not exceed 10,000 bytes.",
"maxProperties": 10000,
"type": "object"
},
"dynamic_template_data": {
"description": "Dynamic template data is available using Handlebars syntax in Dynamic Transactional Templates. This field should be used in combination with a Dynamic Transactional Template, which can be identified by a `template_id` starting with `d-`. This field is a collection of key/value pairs following the pattern \"variable_name\":\"value to insert\".",
"type": "object"
},
"from": {
"$ref": "#/components/schemas/from_email_object"
},
"headers": {
"description": "A collection of JSON key/value pairs allowing you to specify handling instructions for your email. You may not overwrite the following headers: `x-sg-id`, `x-sg-eid`, `received`, `dkim-signature`, `Content-Type`, `Content-Transfer-Encoding`, `To`, `From`, `Subject`, `Reply-To`, `CC`, `BCC`",
"type": "object"
},
"send_at": {
"description": "A unix timestamp allowing you to specify when your email should be delivered. Scheduling delivery more than 72 hours in advance is forbidden.",
"type": "integer"
},
"subject": {
"description": "The subject of your email. See character length requirements according to [RFC 2822](http://stackoverflow.com/questions/1592291/what-is-the-email-subject-length-limit#answer-1592310).",
"minLength": 1,
"type": "string"
},
"substitutions": {
"description": "Substitutions allow you to insert data without using Dynamic Transactional Templates. This field should **not** be used in combination with a Dynamic Transactional Template, which can be identified by a `template_id` starting with `d-`. This field is a collection of key/value pairs following the pattern \"substitution_tag\":\"value to substitute\". The key/value pairs must be strings. These substitutions will apply to the text and html content of the body of your email, in addition to the `subject` and `reply-to` parameters. The total collective size of your substitutions may not exceed 10,000 bytes per personalization object.",
"maxProperties": 10000,
"type": "object"
},
"to": {
"$ref": "#/components/schemas/to_email_array"
}
},
"required": [
"to"
],
"type": "object"
},
"maxItems": 1000,
"type": "array",
"uniqueItems": false
},
"reply_to": {
"$ref": "#/components/schemas/reply_to_email_object"
},
"reply_to_list": {
"description": "An array of recipients who will receive replies and/or bounces. Each object in this array must contain the recipient's email address. Each object in the array may optionally contain the recipient's name. You can either choose to use “reply_to” field or “reply_to_list” but not both.",
"items": {
"properties": {
"email": {
"description": "The email address where any replies or bounces will be returned.",
"format": "email",
"type": "string"
},
"name": {
"description": "A name or title associated with the `reply_to_list` email address.",
"type": "string"
}
},
"required": [
"email"
],
"type": "object"
},
"maxItems": 1000,
"type": "array",
"uniqueItems": true
},
"send_at": {
"description": "A unix timestamp allowing you to specify when you want your email to be delivered. This may be overridden by the `send_at` parameter set at the personalizations level. Delivery cannot be scheduled more than 72 hours in advance. If you have the flexibility, it's better to schedule mail for off-peak times. Most emails are scheduled and sent at the top of the hour or half hour. Scheduling email to avoid peak times — for example, scheduling at 10:53 — can result in lower deferral rates due to the reduced traffic during off-peak times.",
"type": "integer"
},
"subject": {
"description": "The global or 'message level' subject of your email. This may be overridden by subject lines set in personalizations.",
"minLength": 1,
"type": "string"
},
"template_id": {
"description": "An email template ID. A template that contains a subject and content — either text or html — will override any subject and content values specified at the personalizations or message level.",
"type": "string"
},
"tracking_settings": {
"description": "Settings to determine how you would like to track the metrics of how your recipients interact with your email.",
"properties": {
"click_tracking": {
"description": "Allows you to track if a recipient clicked a link in your email.",
"properties": {
"enable": {
"description": "Indicates if this setting is enabled.",
"type": "boolean"
},
"enable_text": {
"description": "Indicates if this setting should be included in the `text/plain` portion of your email.",
"type": "boolean"
}
},
"type": "object"
},
"ganalytics": {
"description": "Allows you to enable tracking provided by Google Analytics.",
"properties": {
"enable": {
"description": "Indicates if this setting is enabled.",
"type": "boolean"
},
"utm_campaign": {
"description": "The name of the campaign.",
"type": "string"
},
"utm_content": {
"description": "Used to differentiate your campaign from advertisements.",
"type": "string"
},
"utm_medium": {
"description": "Name of the marketing medium. (e.g. Email)",
"type": "string"
},
"utm_source": {
"description": "Name of the referrer source. (e.g. Google, SomeDomain.com, or Marketing Email)",
"type": "string"
},
"utm_term": {
"description": "Used to identify any paid keywords.",
"type": "string"
}
},
"type": "object"
},
"open_tracking": {
"description": "Allows you to track if the email was opened by including a single pixel image in the body of the content. When the pixel is loaded, Twilio SendGrid can log that the email was opened.",
"properties": {
"enable": {
"description": "Indicates if this setting is enabled.",
"type": "boolean"
},
"substitution_tag": {
"description": "Allows you to specify a substitution tag that you can insert in the body of your email at a location that you desire. This tag will be replaced by the open tracking pixel.",
"type": "string"
}
},
"type": "object"
},
"subscription_tracking": {
"description": "Allows you to insert a subscription management link at the bottom of the text and HTML bodies of your email. If you would like to specify the location of the link within your email, you may use the `substitution_tag`.",
"properties": {
"enable": {
"description": "Indicates if this setting is enabled.",
"type": "boolean"
},
"html": {
"description": "HTML to be appended to the email with the subscription tracking link. You may control where the link is by using the tag <% %>",
"type": "string"
},
"substitution_tag": {
"description": "A tag that will be replaced with the unsubscribe URL. for example: `[unsubscribe_url]`. If this parameter is used, it will override both the `text` and `html` parameters. The URL of the link will be placed at the substitution tag’s location with no additional formatting.",
"type": "string"
},
"text": {
"description": "Text to be appended to the email with the subscription tracking link. You may control where the link is by using the tag <% %>",
"type": "string"
}
},
"type": "object"
}
},
"type": "object"
}
},
"required": [
"personalizations",
"from",
"subject",
"content"
],
"type": "object"
}
}
}
},
"responses": {
"202": {
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_mailSendErrors_400"
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"413": {
"$ref": "#/components/responses/trait_mailSendErrors_413"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "v3 Mail Send",
"tags": [
"Mail Send"
],
"x-stoplight": {
"id": "POST_mail-send",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/mail_settings": {
"get": {
"description": "**This endpoint allows you to retrieve a list of all mail settings.**\n\nEach setting will be returned with an `enabled` status set to `true` or `false` and a short description that explains what the setting does.",
"operationId": "GET_mail_settings",
"parameters": [
{
"description": "The number of settings to return.",
"in": "query",
"name": "limit",
"schema": {
"type": "integer"
}
},
{
"description": "Where in the list of results to begin displaying settings.",
"in": "query",
"name": "offset",
"schema": {
"type": "integer"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"result": [
{
"description": "Address / domains that should never have email suppressed.",
"enabled": false,
"name": "address_whitelist",
"title": "Address Whitelist"
},
{
"description": "Allows you to automatically purge bounce records from SendGrid after a specified number of days.",
"enabled": false,
"name": "bounce_purge",
"title": "Bounce Purge"
},
{
"description": "Controls notifications for events, such as bounces, clicks, and opens.",
"enabled": true,
"name": "event_notify",
"title": "Event Notification"
},
{
"description": "Allows you to add a custom footer to outgoing email.",
"enabled": false,
"name": "footer",
"title": "Footer"
},
{
"description": "Allows you to forward bounces to a specific email address.",
"enabled": true,
"name": "forward_bounce",
"title": "Forward Bounce"
},
{
"description": "Allows for a copy of spam reports to be forwarded to an email address.",
"enabled": false,
"name": "forward_spam",
"title": "Forward Spam"
},
{
"description": "Allows you to customize your outgoing HTML emails.",
"enabled": true,
"name": "template",
"title": "Legacy Email Template"
},
{
"description": "Convert your plain text emails to HTML.",
"enabled": false,
"name": "plain_content",
"title": "Plain Content"
},
{
"description": "Check outbound messages for spam content.",
"enabled": true,
"name": "spam_check",
"title": "Spam Checker"
}
]
}
}
},
"schema": {
"properties": {
"result": {
"description": "The list of all mail settings.",
"items": {
"properties": {
"description": {
"description": "A description of the mail setting.",
"type": "string"
},
"enabled": {
"description": "Indicates if this mail setting is currently enabled.",
"type": "boolean"
},
"name": {
"description": "The name of the mail setting.",
"type": "string"
},
"title": {
"description": "The title of the mail setting.",
"type": "string"
}
},
"required": [
"title",
"enabled",
"name",
"description"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"result"
],
"type": "object"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_makoErrorResponse_400"
},
"401": {
"$ref": "#/components/responses/trait_makoErrorResponse_401"
},
"403": {
"$ref": "#/components/responses/trait_makoErrorResponse_403"
},
"404": {
"$ref": "#/components/responses/trait_makoErrorResponse_404"
},
"500": {
"$ref": "#/components/responses/trait_makoErrorResponse_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve all mail settings",
"tags": [
"Settings - Mail"
],
"x-stoplight": {
"id": "GET_mailsettings",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/mail_settings/address_whitelist": {
"get": {
"description": "**This endpoint allows you to retrieve your current email address whitelist settings.**\n\nThe Address Whitelist setting allows you to specify email addresses or domains for which mail should never be suppressed.\n\nFor example, if you own the domain `example.com`, and one or more of your recipients use `email@example.com` addresses, placing `example.com` in the address whitelist setting instructs Twilio SendGrid to ignore all bounces, blocks, and unsubscribes logged for that domain. In other words, all bounces, blocks, and unsubscribes will still be sent to `example.com` as if they were sent under normal sending conditions.",
"operationId": "GET_mail_settings-address_whitelist",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"enabled": true,
"list": [
"example.com",
"jane_doe@example1.com"
]
}
}
},
"schema": {
"$ref": "#/components/schemas/mail_settings_address_whitelabel"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_makoErrorResponse_400"
},
"401": {
"$ref": "#/components/responses/trait_makoErrorResponse_401"
},
"403": {
"$ref": "#/components/responses/trait_makoErrorResponse_403"
},
"404": {
"$ref": "#/components/responses/trait_makoErrorResponse_404"
},
"500": {
"$ref": "#/components/responses/trait_makoErrorResponse_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve address whitelist mail settings",
"tags": [
"Settings - Mail"
],
"x-stoplight": {
"id": "GET_mailsettings-addresswhitelist",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"patch": {
"description": "**This endpoint allows you to update your current email address whitelist settings.**\n\nYou can select whether or not this setting should be enabled by assigning the `enabled` field a `true` or `false` value.\n\nPassing only the `enabled` field to this endpoint will not alter your current `list` of whitelist entries. However, any modifications to your `list` of entries will overwrite the entire list. For this reason, you must included all existing entries you wish to retain in your `list` in addition to any new entries you intend to add. To remove one or more `list` entries, pass a `list` with only the entries you wish to retain.\n\nYou should not add generic domains such as `gmail.com` or `yahoo.com` in your `list` because your emails will not honor recipients' unsubscribes. This may cause a legal violation of [CAN-SPAM](https://sendgrid.com/docs/glossary/can-spam/) and could damage your sending reputation.\n\nThe Address Whitelist setting allows you to specify email addresses or domains for which mail should never be suppressed.\n\nFor example, if you own the domain `example.com`, and one or more of your recipients use `email@example.com` addresses, placing `example.com` in the address whitelist setting instructs Twilio SendGrid to ignore all bounces, blocks, and unsubscribes logged for that domain. In other words, all bounces, blocks, and unsubscribes will still be sent to `example.com` as if they were sent under normal sending conditions.",
"operationId": "PATCH_mail_settings-address_whitelist",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"enabled": true,
"list": [
"email1@example.com",
"example.com"
]
},
"properties": {
"enabled": {
"description": "Indicates if your email address whitelist is enabled.",
"type": "boolean"
},
"list": {
"description": "Either a single email address that you want whitelisted or a domain, for which all email addresses belonging to this domain will be whitelisted.",
"items": {
"type": "string"
},
"type": "array"
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"enabled": true,
"list": [
"email1@example.com"
]
}
}
},
"schema": {
"$ref": "#/components/schemas/mail_settings_address_whitelabel"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_makoErrorResponse_400"
},
"401": {
"$ref": "#/components/responses/trait_makoErrorResponse_401"
},
"403": {
"$ref": "#/components/responses/trait_makoErrorResponse_403"
},
"404": {
"$ref": "#/components/responses/trait_makoErrorResponse_404"
},
"500": {
"$ref": "#/components/responses/trait_makoErrorResponse_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update address whitelist mail settings",
"tags": [
"Settings - Mail"
],
"x-stoplight": {
"id": "PATCH_mailsettings-addresswhitelist",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/mail_settings/bounce_purge": {
"get": {
"description": "**This endpoint allows you to retrieve your current bounce and purge settings.**\n\nThe Bounce Perge setting allows you to set a schedule that Twilio SendGrid will use to automatically delete contacts from your soft and hard bounce suppression lists.\n\nA hard bounce occurs when an email message has been returned to the sender because the recipient's address is invalid. A hard bounce might occur because the domain name doesn't exist or because the recipient is unknown.\n\nA soft bounce occurs when an email message reaches the recipient's mail server but is bounced back undelivered before it actually reaches the recipient. A soft bounce might occur because the recipient's inbox is full.\n\nYou can also manage this setting in the [Mail Settings section of the Twilio SendGrid App](https://app.sendgrid.com/settings/mail_settings). You can manage your bounces manually using the [Bounces API](https://sendgrid.api-docs.io/v3.0/bounces-api) or the [Bounces menu in the Twilio SendGrid App](https://app.sendgrid.com/suppressions/bounces).",
"operationId": "GET_mail_settings-bounce_purge",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"enabled": false,
"hard_bounces": 5,
"soft_bounces": 5
}
}
},
"schema": {
"$ref": "#/components/schemas/mail_settings_bounce_purge"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_makoErrorResponse_400"
},
"401": {
"$ref": "#/components/responses/trait_makoErrorResponse_401"
},
"403": {
"$ref": "#/components/responses/trait_makoErrorResponse_403"
},
"404": {
"$ref": "#/components/responses/trait_makoErrorResponse_404"
},
"500": {
"$ref": "#/components/responses/trait_makoErrorResponse_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve bounce purge mail settings",
"tags": [
"Settings - Mail"
],
"x-stoplight": {
"id": "GET_mailsettings-bouncepurge",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"patch": {
"description": "**This endpoint allows you to update your current bounce and purge settings.**\n\nThe Bounce Perge setting allows you to set a schedule that Twilio SendGrid will use to automatically delete contacts from your soft and hard bounce suppression lists. The schedule is set in full days by assigning the number of days, an integer, to the `soft_bounces` and/or `hard_bounces` fields.\n\nA hard bounce occurs when an email message has been returned to the sender because the recipient's address is invalid. A hard bounce might occur because the domain name doesn't exist or because the recipient is unknown.\n\nA soft bounce occurs when an email message reaches the recipient's mail server but is bounced back undelivered before it actually reaches the recipient. A soft bounce might occur because the recipient's inbox is full.\n\nYou can also manage this setting in the [Mail Settings section of the Twilio SendGrid App](https://app.sendgrid.com/settings/mail_settings). You can manage your bounces manually using the [Bounces API](https://sendgrid.api-docs.io/v3.0/bounces-api) or the [Bounces menu in the Twilio SendGrid App](https://app.sendgrid.com/suppressions/bounces).",
"operationId": "PATCH_mail_settings-bounce_purge",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/mail_settings_bounce_purge"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"enabled": false,
"hard_bounces": 5,
"soft_bounces": 5
}
}
},
"schema": {
"$ref": "#/components/schemas/mail_settings_bounce_purge"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_makoErrorResponse_400"
},
"401": {
"$ref": "#/components/responses/trait_makoErrorResponse_401"
},
"403": {
"$ref": "#/components/responses/trait_makoErrorResponse_403"
},
"404": {
"$ref": "#/components/responses/trait_makoErrorResponse_404"
},
"500": {
"$ref": "#/components/responses/trait_makoErrorResponse_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update bounce purge mail settings",
"tags": [
"Settings - Mail"
],
"x-stoplight": {
"id": "PATCH_mailsettings-bouncepurge",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/mail_settings/footer": {
"get": {
"description": "**This endpoint allows you to retrieve your current Footer mail settings.**\n\nThe Footer setting will insert a custom footer at the bottom of your text and HTML email message bodies.\n\nYou can insert your HTML or plain text directly using the \"Update footer mail settings\" endpoint, or you can create the footer using the [Mail Settings menu in the Twilio SendGrid App](https://app.sendgrid.com/settings/mail_settings).",
"operationId": "GET_mail_settings-footer",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"enabled": true,
"html_content": "Ahoy, World!
\n",
"plain_content": ""
}
}
},
"schema": {
"$ref": "#/components/schemas/mail_settings_footer"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_makoErrorResponse_400"
},
"401": {
"$ref": "#/components/responses/trait_makoErrorResponse_401"
},
"403": {
"$ref": "#/components/responses/trait_makoErrorResponse_403"
},
"404": {
"$ref": "#/components/responses/trait_makoErrorResponse_404"
},
"500": {
"$ref": "#/components/responses/trait_makoErrorResponse_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve footer mail settings",
"tags": [
"Settings - Mail"
],
"x-stoplight": {
"id": "GET_mailsettings-footer",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"patch": {
"description": "**This endpoint allows you to update your current Footer mail settings.**\n\nThe Footer setting will insert a custom footer at the bottom of your text and HTML email message bodies.\n\nYou can insert your HTML or plain text directly using this endpoint, or you can create the footer using the [Mail Settings menu in the Twilio SendGrid App](https://app.sendgrid.com/settings/mail_settings).",
"operationId": "PATCH_mail_settings-footer",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/mail_settings_footer"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"enabled": true,
"html_content": "Ahoy, World!
\n",
"plain_content": ""
}
}
},
"schema": {
"$ref": "#/components/schemas/mail_settings_footer"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_makoErrorResponse_400"
},
"401": {
"$ref": "#/components/responses/trait_makoErrorResponse_401"
},
"403": {
"$ref": "#/components/responses/trait_makoErrorResponse_403"
},
"404": {
"$ref": "#/components/responses/trait_makoErrorResponse_404"
},
"500": {
"$ref": "#/components/responses/trait_makoErrorResponse_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update footer mail settings",
"tags": [
"Settings - Mail"
],
"x-stoplight": {
"id": "PATCH_mailsettings-footer",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/mail_settings/forward_bounce": {
"get": {
"description": "**This endpoint allows you to retrieve your current bounce forwarding mail settings.**\n\nEnabling the Forward Bounce setting allows you to specify `email` addresses to which bounce reports will be forwarded. This endpoint returns the email address you have set to receive forwarded bounces and an `enabled` status indicating if the setting is active.",
"operationId": "GET_mail_settings-forward_bounce",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"email": "bounces@example.com",
"enabled": true
}
}
},
"schema": {
"$ref": "#/components/schemas/mail_settings_forward_bounce"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_makoErrorResponse_400"
},
"401": {
"$ref": "#/components/responses/trait_makoErrorResponse_401"
},
"403": {
"$ref": "#/components/responses/trait_makoErrorResponse_403"
},
"404": {
"$ref": "#/components/responses/trait_makoErrorResponse_404"
},
"500": {
"$ref": "#/components/responses/trait_makoErrorResponse_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve forward bounce mail settings",
"tags": [
"Settings - Mail"
],
"x-stoplight": {
"id": "GET_mailsettings-forwardbounce",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"patch": {
"description": "**This endpoint allows you to update your current bounce forwarding mail settings.**\n\nEnabling the Forward Bounce setting allows you to specify an `email` address to which bounce reports will be forwarded.\n\nYou can also configure the Forward Spam mail settings in the [Mail Settings section of the Twilio SendGrid App](https://app.sendgrid.com/settings/mail_settings).",
"operationId": "PATCH_mail_settings-forward_bounce",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/mail_settings_forward_bounce"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"email": "bounces@example.com",
"enabled": true
}
}
},
"schema": {
"$ref": "#/components/schemas/mail_settings_forward_bounce"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_makoErrorResponse_400"
},
"401": {
"$ref": "#/components/responses/trait_makoErrorResponse_401"
},
"403": {
"$ref": "#/components/responses/trait_makoErrorResponse_403"
},
"404": {
"$ref": "#/components/responses/trait_makoErrorResponse_404"
},
"500": {
"$ref": "#/components/responses/trait_makoErrorResponse_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update forward bounce mail settings",
"tags": [
"Settings - Mail"
],
"x-stoplight": {
"id": "PATCH_mailsettings-forwardbounce",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/mail_settings/forward_spam": {
"get": {
"description": "**This endpoint allows you to retrieve your current Forward Spam mail settings.**\n\nEnabling the Forward Spam setting allows you to specify `email` addresses to which spam reports will be forwarded. This endpoint returns any email address(es) you have set to receive forwarded spam and an `enabled` status indicating if the setting is active.",
"operationId": "GET_mail_settings-forward_spam",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"email": "abuse@example.com",
"enabled": true
}
}
},
"schema": {
"$ref": "#/components/schemas/mail_settings_forward_spam"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_makoErrorResponse_400"
},
"401": {
"$ref": "#/components/responses/trait_makoErrorResponse_401"
},
"403": {
"$ref": "#/components/responses/trait_makoErrorResponse_403"
},
"404": {
"$ref": "#/components/responses/trait_makoErrorResponse_404"
},
"500": {
"$ref": "#/components/responses/trait_makoErrorResponse_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve forward spam mail settings",
"tags": [
"Settings - Mail"
],
"x-stoplight": {
"id": "GET_mailsettings-forwardspam",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"patch": {
"description": "**This endpoint allows you to update your current Forward Spam mail settings.**\n\nEnabling the Forward Spam setting allows you to specify `email` addresses to which spam reports will be forwarded. You can set multiple addresses by passing this endpoint a comma separated list of emails in a single string.\n\n```\n{\n \"email\": \"address1@example.com, address2@exapmle.com\",\n \"enabled\": true\n}\n```\n\nThe Forward Spam setting may also be used to receive emails sent to `abuse@` and `postmaster@` role addresses if you have authenticated your domain.\n\nFor example, if you authenticated `example.com` as your root domain and set a custom return path of `sub` for that domain, you could turn on Forward Spam, and any emails sent to `abuse@sub.example.com` or `postmaster@sub.example.com` would be forwarded to the email address you entered in the `email` field.\n\nYou can authenticate your domain using the \"Authenticate a domain\" endpoint or in the [Sender Authentication section of the Twilio SendGrid App](https://app.sendgrid.com/settings/sender_auth). You can also configure the Forward Spam mail settings in the [Mail Settings section of the Twilio SendGrid App](https://app.sendgrid.com/settings/mail_settings).",
"operationId": "PATCH_mail_settings-forward_spam",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/mail_settings_forward_spam"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"email": "abuse@example.com",
"enabled": true
}
}
},
"schema": {
"$ref": "#/components/schemas/mail_settings_forward_spam"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_makoErrorResponse_400"
},
"401": {
"$ref": "#/components/responses/trait_makoErrorResponse_401"
},
"403": {
"$ref": "#/components/responses/trait_makoErrorResponse_403"
},
"404": {
"$ref": "#/components/responses/trait_makoErrorResponse_404"
},
"500": {
"$ref": "#/components/responses/trait_makoErrorResponse_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update forward spam mail settings",
"tags": [
"Settings - Mail"
],
"x-stoplight": {
"id": "PATCH_mailsettings-forwardspam",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/mail_settings/template": {
"get": {
"description": "**This endpoint allows you to retrieve your current legacy email template settings.**\n\nThis setting refers to our original email templates. We currently support more fully featured [Dynamic Transactional Templates](https://sendgrid.com/docs/ui/sending-email/how-to-send-an-email-with-dynamic-transactional-templates/).\n\nThe legacy email template setting wraps an HTML template around your email content. This can be useful for sending out marketing email and/or other HTML formatted messages. For instructions on using legacy templates, see how to [\"Create and Edit Legacy Transactional Templates](https://sendgrid.com/docs/ui/sending-email/create-and-edit-legacy-transactional-templates/). For help migrating to our current template system, see [\"Migrating from Legacy Templates\"](https://sendgrid.com/docs/ui/sending-email/migrating-from-legacy-templates/).",
"operationId": "GET_mail_settings-template",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"enabled": false,
"html_content": "<% body %>Example
\n"
}
}
},
"schema": {
"$ref": "#/components/schemas/mail_settings_template"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_makoErrorResponse_400"
},
"401": {
"$ref": "#/components/responses/trait_makoErrorResponse_401"
},
"403": {
"$ref": "#/components/responses/trait_makoErrorResponse_403"
},
"404": {
"$ref": "#/components/responses/trait_makoErrorResponse_404"
},
"500": {
"$ref": "#/components/responses/trait_makoErrorResponse_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve legacy template mail settings",
"tags": [
"Settings - Mail"
],
"x-stoplight": {
"id": "GET_mailsettings-template",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"patch": {
"description": "**This endpoint allows you to update your current legacy email template settings.**\n\nThis setting refers to our original email templates. We currently support more fully featured [Dynamic Transactional Templates](https://sendgrid.com/docs/ui/sending-email/how-to-send-an-email-with-dynamic-transactional-templates/).\n\nThe legacy email template setting wraps an HTML template around your email content. This can be useful for sending out marketing email and/or other HTML formatted messages. For instructions on using legacy templates, see how to [\"Create and Edit Legacy Transactional Templates](https://sendgrid.com/docs/ui/sending-email/create-and-edit-legacy-transactional-templates/). For help migrating to our current template system, see [\"Migrating from Legacy Templates\"](https://sendgrid.com/docs/ui/sending-email/migrating-from-legacy-templates/).",
"operationId": "PATCH_mail_settings-template",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"enabled": true,
"html_content": "<% body %>"
},
"properties": {
"enabled": {
"description": "Indicates if you want to enable the legacy email template mail setting.",
"type": "boolean"
},
"html_content": {
"description": "The new HTML content for your legacy email template.",
"type": "string"
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"enabled": false,
"html_content": "<% body %>Example
\n"
}
}
},
"schema": {
"properties": {
"enabled": {
"description": "Indicates if the legacy email template mail setting is enabled.",
"type": "boolean"
},
"html_content": {
"description": "The HTML content of your legacy email template.",
"type": "string"
}
},
"required": [
"enabled",
"html_content"
],
"type": "object"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_makoErrorResponse_400"
},
"401": {
"$ref": "#/components/responses/trait_makoErrorResponse_401"
},
"403": {
"$ref": "#/components/responses/trait_makoErrorResponse_403"
},
"404": {
"$ref": "#/components/responses/trait_makoErrorResponse_404"
},
"500": {
"$ref": "#/components/responses/trait_makoErrorResponse_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update template mail settings",
"tags": [
"Settings - Mail"
],
"x-stoplight": {
"id": "PATCH_mailsettings-template",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/mailbox_providers/stats": {
"get": {
"description": "**This endpoint allows you to retrieve your email statistics segmented by recipient mailbox provider.**\n\n**We only store up to 7 days of email activity in our database.** By default, 500 items will be returned per request via the Advanced Stats API endpoints.\n\nAdvanced Stats provide a more in-depth view of your email statistics and the actions taken by your recipients. You can segment these statistics by geographic location, device type, client type, browser, and mailbox provider. For more information about statistics, please see our [Statistics Overview](https://sendgrid.com/docs/ui/analytics-and-reporting/stats-overview/).",
"operationId": "GET_mailbox_providers-stats",
"parameters": [
{
"description": "The mail box providers to get statistics for. You can include up to 10 by including this parameter multiple times.",
"in": "query",
"name": "mailbox_providers",
"schema": {
"type": "string"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
},
{
"$ref": "#/components/parameters/trait_statsAdvancedQueryStringsLimitOffset_limit"
},
{
"$ref": "#/components/parameters/trait_statsAdvancedQueryStringsLimitOffset_offset"
},
{
"$ref": "#/components/parameters/trait_statsAdvancedQueryStringsLimitOffset_aggregated_by"
},
{
"$ref": "#/components/parameters/trait_statsAdvancedQueryStringsLimitOffset_start_date"
},
{
"$ref": "#/components/parameters/trait_statsAdvancedQueryStringsLimitOffset_end_date"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"date": "2015-10-11",
"stats": [
{
"metrics": {
"blocks": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"drops": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "Gmail",
"type": "mailbox_provider"
}
]
},
{
"date": "2015-10-12",
"stats": [
{
"metrics": {
"blocks": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"drops": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "Gmail",
"type": "mailbox_provider"
}
]
},
{
"date": "2015-10-13",
"stats": [
{
"metrics": {
"blocks": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"drops": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "Gmail",
"type": "mailbox_provider"
}
]
},
{
"date": "2015-10-14",
"stats": [
{
"metrics": {
"blocks": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"drops": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "Gmail",
"type": "mailbox_provider"
}
]
},
{
"date": "2015-10-15",
"stats": [
{
"metrics": {
"blocks": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"drops": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "Gmail",
"type": "mailbox_provider"
}
]
},
{
"date": "2015-10-16",
"stats": [
{
"metrics": {
"blocks": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"drops": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "Gmail",
"type": "mailbox_provider"
}
]
},
{
"date": "2015-10-17",
"stats": [
{
"metrics": {
"blocks": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"drops": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "Gmail",
"type": "mailbox_provider"
}
]
},
{
"date": "2015-10-18",
"stats": [
{
"metrics": {
"blocks": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"drops": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "Gmail",
"type": "mailbox_provider"
}
]
},
{
"date": "2015-10-19",
"stats": [
{
"metrics": {
"blocks": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"drops": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "Gmail",
"type": "mailbox_provider"
}
]
},
{
"date": "2015-10-20",
"stats": [
{
"metrics": {
"blocks": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"drops": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "Gmail",
"type": "mailbox_provider"
}
]
},
{
"date": "2015-10-21",
"stats": [
{
"metrics": {
"blocks": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 1,
"drops": 0,
"opens": 1,
"processed": 0,
"requests": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 1
},
"name": "Gmail",
"type": "mailbox_provider"
}
]
},
{
"date": "2015-10-22",
"stats": [
{
"metrics": {
"blocks": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"drops": 0,
"opens": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "Gmail",
"type": "mailbox_provider"
}
]
},
{
"date": "2015-10-23",
"stats": [
{
"metrics": {
"blocks": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"drops": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "Gmail",
"type": "mailbox_provider"
}
]
},
{
"date": "2015-10-24",
"stats": [
{
"metrics": {
"blocks": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"drops": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "Gmail",
"type": "mailbox_provider"
}
]
},
{
"date": "2015-10-25",
"stats": [
{
"metrics": {
"blocks": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"drops": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "Gmail",
"type": "mailbox_provider"
}
]
},
{
"date": "2015-10-26",
"stats": [
{
"metrics": {
"blocks": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 2,
"drops": 0,
"opens": 2,
"processed": 0,
"requests": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 2
},
"name": "Gmail",
"type": "mailbox_provider"
}
]
},
{
"date": "2015-10-27",
"stats": [
{
"metrics": {
"blocks": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"drops": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "Gmail",
"type": "mailbox_provider"
}
]
},
{
"date": "2015-10-28",
"stats": [
{
"metrics": {
"blocks": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"drops": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "Gmail",
"type": "mailbox_provider"
}
]
},
{
"date": "2015-10-29",
"stats": [
{
"metrics": {
"blocks": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"drops": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "Gmail",
"type": "mailbox_provider"
}
]
},
{
"date": "2015-10-30",
"stats": [
{
"metrics": {
"blocks": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"drops": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "Gmail",
"type": "mailbox_provider"
}
]
},
{
"date": "2015-10-31",
"stats": [
{
"metrics": {
"blocks": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"drops": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "Gmail",
"type": "mailbox_provider"
}
]
},
{
"date": "2015-11-01",
"stats": [
{
"metrics": {
"blocks": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"drops": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "Gmail",
"type": "mailbox_provider"
}
]
},
{
"date": "2015-11-02",
"stats": [
{
"metrics": {
"blocks": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"drops": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "Gmail",
"type": "mailbox_provider"
}
]
},
{
"date": "2015-11-03",
"stats": [
{
"metrics": {
"blocks": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"drops": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "Gmail",
"type": "mailbox_provider"
}
]
},
{
"date": "2015-11-04",
"stats": [
{
"metrics": {
"blocks": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"drops": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "Gmail",
"type": "mailbox_provider"
}
]
},
{
"date": "2015-11-05",
"stats": [
{
"metrics": {
"blocks": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"drops": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "Gmail",
"type": "mailbox_provider"
}
]
},
{
"date": "2015-11-06",
"stats": [
{
"metrics": {
"blocks": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"drops": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "Gmail",
"type": "mailbox_provider"
}
]
},
{
"date": "2015-11-07",
"stats": [
{
"metrics": {
"blocks": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"drops": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "Gmail",
"type": "mailbox_provider"
}
]
},
{
"date": "2015-11-08",
"stats": [
{
"metrics": {
"blocks": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"drops": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "Gmail",
"type": "mailbox_provider"
}
]
},
{
"date": "2015-11-09",
"stats": [
{
"metrics": {
"blocks": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"drops": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "Gmail",
"type": "mailbox_provider"
}
]
},
{
"date": "2015-11-10",
"stats": [
{
"metrics": {
"blocks": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"drops": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0
},
"name": "Gmail",
"type": "mailbox_provider"
}
]
}
]
}
},
"schema": {
"items": {
"properties": {
"date": {
"description": "The date that the statistics were gathered.",
"type": "string"
},
"stats": {
"description": "The list of statistics.",
"items": {
"properties": {
"metrics": {
"$ref": "#/components/schemas/advanced_stats_mailbox_provider"
},
"name": {
"description": "The name of the specific segmentation.",
"type": "string"
},
"type": {
"description": "The type of segmentation.",
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve email statistics by mailbox provider.",
"tags": [
"Stats"
],
"x-stoplight": {
"id": "GET_mailboxproviders-stats",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/marketing/contacts": {
"delete": {
"description": "**This endpoint can be used to delete one or more contacts**.\n\nThe query parameter `ids` must set to a comma-separated list of contact IDs for bulk contact deletion.\n\nThe query parameter `delete_all_contacts` must be set to `\"true\"` to delete **all** contacts. \n\nYou must set either `ids` or `delete_all_contacts`.\n\nDeletion jobs are processed asynchronously.\n\nTwilio SendGrid recommends exporting your contacts regularly as a backup to avoid issues or lost data.",
"operationId": "DELETE_mc-contacts",
"parameters": [
{
"description": "Must be set to `\"true\"` to delete all contacts.",
"in": "query",
"name": "delete_all_contacts",
"required": false,
"schema": {
"type": "string"
}
},
{
"description": "A comma-separated list of contact IDs.",
"in": "query",
"name": "ids",
"required": false,
"schema": {
"type": "string"
}
}
],
"responses": {
"202": {
"content": {
"application/json": {
"schema": {
"description": "The deletion job has been accepted and is being processed.",
"properties": {
"job_id": {
"description": "The deletion job ID.",
"type": "object"
}
},
"required": [
"job_id"
],
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"type": "object"
},
"type": "array"
}
},
"required": [
"errors"
],
"type": "object"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete Contacts",
"tags": [
"Contacts"
],
"x-stoplight": {
"id": "DELETE_mc-contacts",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"get": {
"description": "**This endpoint will return up to 50 of the most recent contacts uploaded or attached to a list**. \n\nThis list will then be sorted by email address.\n\nThe full contact count is also returned.\n\nPlease note that pagination of the contacts has been deprecated.\n\nTwilio SendGrid recommends exporting your contacts regularly as a backup to avoid issues or lost data.",
"operationId": "GET_mc-contats",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"properties": {
"_metadata": {
"$ref": "#/components/schemas/selfmetadata"
},
"contact_count": {
"type": "integer"
},
"result": {
"items": {
"$ref": "#/components/schemas/contact-details3"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"$ref": "#/components/schemas/error"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": ""
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Get Sample Contacts",
"tags": [
"Contacts"
],
"x-stoplight": {
"id": "GET_mc-contats",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"put": {
"description": "**This endpoint allows the [upsert](https://en.wiktionary.org/wiki/upsert) (insert or update) of up to 30,000 contacts, or 6MB of data, whichever is lower**. \n\nBecause the creation and update of contacts is an asynchronous process, the response will not contain immediate feedback on the processing of your upserted contacts. Rather, it will contain an HTTP 202 response indicating the contacts are queued for processing or an HTTP 4XX error containing validation errors. Should you wish to get the resulting contact's ID or confirm your contacts have been updated or added, you can use the \"Get Contacts by Emails\" endpoint. \n\nPlease note that custom fields need to have been already created if you wish to set their values for the contacts being upserted. To do this, please use the \"Create Custom Field Definition\" endpoint.\n\nYou will see a `job_id` in the response to your request. This can be used to check the status of your upsert job. To do so, please use the \"Import Contacts Status\" endpoint.\n\nIf the contact already exists in the system, any entries submitted via this endpoint will update the existing contact. The contact to update will be determined only by the `email` field and any fields omitted from the request will remain as they were. A contact's ID cannot be used to update the contact.\n\nThe email field will be changed to all lower-case. If a contact is added with an email that exists but contains capital letters, the existing contact with the all lower-case email will be updated.",
"operationId": "PUT_mc-contacts",
"requestBody": {
"content": {
"application/json": {
"schema": {
"properties": {
"contacts": {
"description": "One or more contacts objects that you intend to upsert. The available fields for a contact, including the required `email` field are described below.",
"items": {
"$ref": "#/components/schemas/contact-request"
},
"maxItems": 30000,
"minItems": 1,
"type": "array"
},
"list_ids": {
"description": "An array of List ID strings that this contact will be added to.",
"items": {
"format": "uuid",
"type": "string"
},
"type": "array"
}
},
"required": [
"contacts"
],
"type": "object"
}
}
}
},
"responses": {
"202": {
"content": {
"application/json": {
"schema": {
"properties": {
"job_id": {
"description": "Indicates that the contacts are queued for processing. Check the job status with the \"Import Contacts Status\" endpoint.",
"type": "string"
}
},
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"$ref": "#/components/schemas/error"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Add or Update a Contact",
"tags": [
"Contacts"
],
"x-stoplight": {
"id": "PUT_mc-contacts",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/marketing/contacts/batch": {
"post": {
"description": "**This endpoint is used to retrieve a set of contacts identified by their IDs.**\n\nThis can be more efficient endpoint to get contacts than making a series of individual `GET` requests to the \"Get a Contact by ID\" endpoint.\n\nYou can supply up to 100 IDs. Pass them into the `ids` field in your request body as an array or one or more strings.\n\nTwilio SendGrid recommends exporting your contacts regularly as a backup to avoid issues or lost data.",
"operationId": "POST_marketing-contacts-batch",
"requestBody": {
"content": {
"application/json": {
"schema": {
"description": "Array of IDs",
"example": {
"ids": [
"1234",
"1235"
]
},
"properties": {
"ids": {
"items": {
"type": "string"
},
"maxItems": 100,
"type": "array"
}
},
"required": [
"ids"
],
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"properties": {
"result": {
"items": {
"$ref": "#/components/schemas/contact-details3"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": ""
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"summary": "Get Batched Contacts by IDs",
"tags": [
"Contacts"
],
"x-stoplight": {
"id": "POST_marketing-contacts-batch",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/marketing/contacts/count": {
"get": {
"description": "**This endpoint returns the total number of contacts you have stored.**\n\n\nTwilio SendGrid recommends exporting your contacts regularly as a backup to avoid issues or lost data.",
"operationId": "GET_mc-contacts-count",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"properties": {
"billable_breakdown": {
"description": "`billable_breakdown` will only appear to the parent user in an account with subusers.",
"properties": {
"breakdown": {
"description": "A map of each subuser's billable contact usage. Each key is the subuser's ID and each value is the usage thus far this month.",
"minProperties": 0,
"type": "object"
},
"total": {
"description": "The sum of all the subuser's billable contacts",
"type": "integer"
}
},
"type": "object"
},
"billable_count": {
"default": 0,
"description": "The count of contacts this month for billing purposes.",
"minimum": 0,
"type": "integer"
},
"contact_count": {
"description": "The total number of contacts.",
"type": "integer"
}
},
"required": [
"contact_count"
],
"type": "object"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Get Total Contact Count",
"tags": [
"Contacts"
],
"x-stoplight": {
"id": "GET_mc-contacts-count",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/marketing/contacts/exports": {
"get": {
"description": "**Use this endpoint to retrieve details of all current exported jobs**.\n\nIt will return an array of objects, each of which records an export job in flight or recently completed. \n\nEach object's `export_type` field will tell you which kind of export it is and its `status` field will indicate what stage of processing it has reached. Exports which are `ready` will be accompanied by a `urls` field which lists the URLs of the export's downloadable files — there will be more than one if you specified a maximum file size in your initial export request.\n\nUse this endpoint if you have exports in flight but do not know their IDs, which are required for the \"Export Contacts Status\" endpoint.",
"operationId": "GET_marketing-contacts-exports",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"properties": {
"_metadata": {
"properties": {
"next": {
"description": "Link to next page.",
"type": "string"
},
"prev": {
"type": "string"
},
"self": {
"description": "Link to this page.",
"type": "string"
}
},
"type": "object"
},
"result": {
"items": {
"properties": {
"_metadata": {
"properties": {
"next": {
"type": "string"
},
"prev": {
"type": "string"
},
"self": {
"type": "string"
}
},
"type": "object"
},
"completed_at": {
"description": "This ISO8601 timestamp when the export was completed.",
"type": "string"
},
"created_at": {
"description": "This ISO8601 timestamp when the export was created.",
"type": "string"
},
"expires_at": {
"description": "This ISO8601 timestamp when the export expires.",
"type": "string"
},
"export_type": {
"description": "Allowed types: `contacts_export`, `list_export`, or `segment_export`.",
"type": "string"
},
"id": {
"description": "Export jobs ID.",
"type": "string"
},
"lists": {
"items": {
"properties": {
"ID": {
"type": "string"
},
"Name": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
},
"segments": {
"items": {
"properties": {
"ID": {
"type": "string"
},
"Name": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
},
"status": {
"description": "Allowed values: `pending`, `ready`, or `failure`.",
"type": "string"
},
"urls": {
"description": "One or more download URLs for the contact file(s) if the status is `ready`.",
"items": {
"type": "string"
},
"type": "array"
},
"user_id": {
"description": "User ID.",
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"": {
"type": "string"
},
"error_id": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"message"
],
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Get All Existing Exports",
"tags": [
"Contacts"
],
"x-stoplight": {
"id": "GET_marketing-contacts-exports",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"post": {
"description": "**Use this endpoint to export lists or segments of contacts**.\n\nIf you would just like to have a link to the exported list sent to your email set the `notifications.email` option to `true` in the `POST` payload.\n\nIf you would like to download the list, take the `id` that is returned and use the \"Export Contacts Status\" endpoint to get the `urls`. Once you have the list of URLs, make a `GET` request to each URL provided to download your CSV file(s).\n\nYou specify the segements and or/contact lists you wish to export by providing the relevant IDs in, respectively, the `segment_ids` and `list_ids` fields in the request body.\n\nThe lists will be provided in either JSON or CSV files. To specify which of these you would required, set the request body `file_type` field to `json` or `csv`.\n\nYou can also specify a maximum file size (in MB). If the export file is larger than this, it will be split into multiple files.",
"operationId": "POST_mc-contacts-exports",
"requestBody": {
"content": {
"application/json": {
"schema": {
"properties": {
"file_type": {
"default": "csv",
"description": "File type for export file. Choose from `json` or `csv`.",
"enum": [
"csv",
"json"
],
"type": "string"
},
"list_ids": {
"description": "IDs of the contact lists you want to export.",
"items": {
"format": "uuid",
"type": "string"
},
"type": "array"
},
"max_file_size": {
"default": 5000,
"description": "The maximum size of an export file in MB. Note that when this option is specified, multiple output files may be returned from the export.",
"type": "integer"
},
"notifications": {
"properties": {
"email": {
"type": "boolean"
}
},
"type": "object"
},
"segment_ids": {
"description": "IDs of the contact segments you want to export.",
"items": {
"type": "string"
},
"type": "array"
}
},
"type": "object"
}
}
}
},
"responses": {
"202": {
"content": {
"application/json": {
"schema": {
"properties": {
"_metadata": {
"$ref": "#/components/schemas/metadata"
},
"id": {
"description": "The ID of the export job.",
"type": "string"
}
},
"required": [
"_metadata"
],
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"$ref": "#/components/schemas/error"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Export Contacts",
"tags": [
"Contacts"
],
"x-stoplight": {
"id": "POST_mc-contacts-exports",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/marketing/contacts/exports/{id}": {
"get": {
"description": "**This endpoint can be used to check the status of a contact export job**. \n\nTo use this call, you will need the `id` from the \"Export Contacts\" call.\n\nIf you would like to download a list, take the `id` that is returned from the \"Export Contacts\" endpoint and make an API request here to get the `urls`. Once you have the list of URLs, make a `GET` request on each URL to download your CSV file(s).\n\nTwilio SendGrid recommends exporting your contacts regularly as a backup to avoid issues or lost data.",
"operationId": "GET_mc-contacts-exports-id",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/contact-export"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"$ref": "#/components/schemas/error"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Export Contacts Status",
"tags": [
"Contacts"
],
"x-stoplight": {
"id": "GET_mc-contacts-exports-id",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
]
},
"/marketing/contacts/imports": {
"put": {
"description": "**This endpoint allows a CSV upload containing up to one million contacts or 5GB of data, whichever is smaller.**\n\nImports take place asynchronously: the endpoint returns a URL (`upload_uri`) and HTTP headers (`upload_headers`) which can subsequently be used to `PUT` a file of contacts to be imported into our system.\n\nUploaded CSV files may also be [gzip-compressed](https://en.wikipedia.org/wiki/Gzip).\n\nIn either case, you must include the field `file_type` with the value `csv` in your request body.\n\nThe `field_mappings` paramter is a respective list of field definition IDs to map the uploaded CSV columns to. It allows you to use CSVs where one or more columns are skipped (`null`) or remapped to the contact field. \n\nFor example, if `field_mappings` is set to `[null, \"w1\", \"_rf1\"]`, this means skip column 0, map column 1 to the custom field with the ID `w1`, and map column 2 to the reserved field with the ID `_rf1`. See the \"Get All Field Definitions\" endpoint to fetch your custom and reserved field IDs to use with `field_mappings`.\n\nOnce you recieve the response body you can then initiate a **second** API call where you use the supplied URL and HTTP header to upload your file. For example:\n\n`curl --upload-file \"file/path.csv\" \"URL_GIVEN\" -H 'HEADER_GIVEN'`\n\nIf you'd like to monitor the status of your import job, use the `job_id` and the \"Import Contacts Status\" endpoint.\n\nTwilio SendGrid recommends exporting your contacts regularly as a backup to avoid issues or lost data.",
"operationId": "PUT_mc-contacts-imports",
"requestBody": {
"content": {
"application/json": {
"schema": {
"properties": {
"field_mappings": {
"description": "Import file header to reserved/custom field mapping.",
"items": {
"anyOf": [
{
"type": "string"
},
{
"nullable": true
}
]
},
"minItems": 1,
"type": "array",
"uniqueItems": false
},
"file_type": {
"description": "Upload file type.",
"enum": [
"csv"
],
"type": "string"
},
"list_ids": {
"description": "All contacts will be added to each of the specified lists.",
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"file_type",
"field_mappings"
],
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"properties": {
"job_id": {
"description": "The ID of the import job.",
"type": "string"
},
"upload_headers": {
"description": "A list of headers that must be included in PUT request.",
"items": {
"properties": {
"header": {
"type": "string"
},
"value": {
"type": "string"
}
},
"required": [
"header",
"value"
],
"type": "object"
},
"type": "array"
},
"upload_uri": {
"description": "The URI to PUT the upload file to.",
"type": "string"
}
},
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"$ref": "#/components/schemas/error"
},
"type": "array",
"uniqueItems": true
}
},
"required": [
"errors"
],
"type": "object"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"content": {
"application/json": {
"schema": {
"description": "If any of the specified lists do not exist.",
"properties": {
"errors": {
"items": {
"$ref": "#/components/schemas/error"
},
"type": "array",
"uniqueItems": true
}
},
"required": [
"errors"
],
"type": "object"
}
}
},
"description": ""
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Import Contacts",
"tags": [
"Contacts"
],
"x-stoplight": {
"id": "PUT_mc-contacts-imports",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/marketing/contacts/imports/{id}": {
"get": {
"description": "**This endpoint can be used to check the status of a contact import job**. \n\nUse the `job_id` from the \"Import Contacts,\" \"Add or Update a Contact,\" or \"Delete Contacts\" endpoints as the `id` in the path parameter.\n\nIf there is an error with your `PUT` request, download the `errors_url` file and open it to view more details.\n\nThe job `status` field indicates whether the job is `pending`, `completed`, `errored`, or `failed`. \n\nPending means not started. Completed means finished without any errors. Errored means finished with some errors. Failed means finshed with all errors, or the job was entirely unprocessable: for example, if you attempt to import file format we do not support.\n\nThe `results` object will have fields depending on the job type.\n\nTwilio SendGrid recommends exporting your contacts regularly as a backup to avoid issues or lost data.",
"operationId": "GET_marketing-contacts-imports-id",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/contact-import"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"$ref": "#/components/schemas/error"
}
},
"type": "object"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Import Contacts Status",
"tags": [
"Contacts"
],
"x-stoplight": {
"id": "GET_marketing-contacts-imports-id",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
]
},
"/marketing/contacts/search": {
"post": {
"description": "**Use this endpoint to locate contacts**.\n\nThe request body's `query` field accepts valid [SGQL](https://sendgrid.com/docs/for-developers/sending-email/segmentation-query-language/) for searching for a contact.\n\nBecause contact emails are stored in lower case, using SGQL to search by email address requires the provided email address to be in lower case. The SGQL `lower()` function can be used for this.\n\nOnly the first 50 contacts that meet the search criteria will be returned.\n\nIf the query takes longer than 20 seconds, a `408 Request Timeout` status will be returned.\n\nFormatting the `created_at` and `updated_at` values as Unix timestamps is deprecated. Instead they are returned as ISO format as string.",
"operationId": "POST_mc-contacts-search",
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"query": "email LIKE 'ENTER_COMPLETE_OR_PARTIAL_EMAIL_ADDRESS_HERE%' AND CONTAINS(list_ids, 'YOUR_LIST_IDs')"
},
"properties": {
"query": {
"description": "An SGQL search string or other pattern.",
"type": "string"
}
},
"required": [
"query"
],
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"properties": {
"_metadata": {
"$ref": "#/components/schemas/selfmetadata"
},
"contact_count": {
"description": "The total number of contacts matched.",
"type": "number"
},
"result": {
"items": {
"$ref": "#/components/schemas/contact-details3"
},
"type": "array"
}
},
"required": [
"contact_count"
],
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"408": {
"description": ""
},
"500": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Search Contacts",
"tags": [
"Contacts"
],
"x-stoplight": {
"id": "POST_mc-contacts-search",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/marketing/contacts/search/emails": {
"post": {
"description": "**This endpoint allows you to retrieve up to 100 contacts matching the searched `email` address(es), including any `alternate_emails`.** \n\nEmail addresses are unique to a contact, meaning this endpoint can treat an email address as a primary key to search by. The contact object associated with the address, whether it is their `email` or one of their `alternate_emails` will be returned if matched.\n\nEmail addresses in the search request do not need to match the case in which they're stored, but the email addresses in the result will be all lower case. Empty strings are excluded from the search and will not be returned.\n\nThis endpoint should be used in place of the \"Search Contacts\" endpoint when you can provide exact email addresses and do not need to include other [Segmentation Query Language (SGQL)](https://sendgrid.com/docs/for-developers/sending-email/segmentation-query-language/) filters when searching.\n\nIf you need to access a large percentage of your contacts, we recommend exporting your contacts with the \"Export Contacts\" endpoint and filtering the results client side.\n\nThis endpoint returns a `200` status code when any contacts match the address(es) you supplied. When searching multiple addresses in a single request, it is possible that some addresses will match a contact while others will not. When a partially successful search like this is made, the matching contacts are returned in an object and an error message is returned for the email address(es) that are not found. \n\nThis endpoint returns a `404` status code when no contacts are found for the provided email address(es).\n\nA `400` status code is returned if any searched addresses are invalid.\n\nTwilio SendGrid recommends exporting your contacts regularly as a backup to avoid issues or lost data.",
"operationId": "POST_marketing-contacts-search-emails",
"requestBody": {
"content": {
"application/json": {
"schema": {
"description": "",
"example": {
"emails": [
"jane_doe@example.com",
"john_doe@example.com",
"joann_doe@example.com"
]
},
"properties": {
"emails": {
"description": "One or more primary emails and/or alternate emails to search through your contacts for.",
"items": {
"maxLength": 100,
"type": "string"
},
"type": "array"
}
},
"required": [
"emails"
],
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"result": {
"jane_doe@example.com": {
"contact": {
"_metadata": {
"self": ""
},
"address_line_1": "",
"address_line_2": "",
"alternate_emails": [
"janedoe@example1.com"
],
"city": "",
"country": "",
"created_at": "2021-03-02T15:25:47Z",
"custom_fields": {},
"email": "jane_doe@example.com",
"facebook": "",
"first_name": "Jane",
"id": "asdf-Jkl-zxCvBNm",
"last_name": "Doe",
"line": "",
"list_ids": [],
"phone_number": "",
"postal_code": "",
"segment_ids": [],
"state_province_region": "",
"unique_name": "",
"updated_at": "2021-03-30T15:26:16Z",
"whatsapp": ""
}
},
"joann_doe@example.com": {
"error": "contact not found"
},
"john_doe@example.com": {
"contact": {
"_metadata": {
"self": ""
},
"address_line_1": "",
"address_line_2": "",
"alternate_emails": [],
"city": "",
"country": "",
"created_at": "2020-01-02T15:25:47Z",
"custom_fields": {},
"email": "john_doe@example.com",
"facebook": "",
"first_name": "Jane",
"id": "asdf-Jkl-qWeRTy",
"last_name": "Doe",
"line": "",
"list_ids": [],
"phone_number": "",
"postal_code": "",
"segment_ids": [],
"state_province_region": "",
"unique_name": "",
"updated_at": "2020-12-20T15:26:16Z",
"whatsapp": ""
}
}
}
}
}
},
"schema": {
"properties": {
"result": {
"additionalProperties": false,
"patternProperties": {
"^[A-Za-z0-9\\._%\\+-]+@[A-Za-z0-9\\.-]+\\.[A-Za-z]{2,6}$": {
"description": "This will be one of the specified email adresses.",
"properties": {
"contact": {
"$ref": "#/components/schemas/contact-details3"
},
"error": {
"type": "string"
}
},
"type": "object"
}
},
"type": "object"
}
},
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"500": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Get Contacts by Emails",
"tags": [
"Contacts"
],
"x-stoplight": {
"id": "POST_marketing-contacts-search-emails",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/marketing/contacts/{id}": {
"get": {
"description": "**This endpoint returns the full details and all fields for the specified contact**.\n\nThe \"Get Contacts by Emails\" endpoint can be used to get the ID of a contact.",
"operationId": "GET_mc-contacts-id",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/contact-details3"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"description": ""
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Get a Contact by ID",
"tags": [
"Contacts"
],
"x-stoplight": {
"id": "GET_mc-contacts-id",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
]
},
"/marketing/field_definitions": {
"get": {
"description": "**This endpoint retrieves all defined Custom Fields and Reserved Fields.**",
"operationId": "GET_mc-field_definitions",
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"_metadata": {
"self": "https://example.com/marketing/field_definitions"
},
"custom_fields": [
{
"field_type": "Number",
"id": "w1",
"name": "num_orders"
},
{
"field_type": "Date",
"id": "w2",
"name": "dob"
}
],
"reserved_fields": [
{
"field_type": "Text",
"id": "_rf0_T",
"name": "first_name"
},
{
"field_type": "Text",
"id": "_rf1_T",
"name": "last_name"
},
{
"field_type": "Text",
"id": "_rf2_T",
"name": "email"
},
{
"field_type": "Text",
"id": "_rf3_T",
"name": "alternate_emails"
},
{
"field_type": "Text",
"id": "_rf4_T",
"name": "address_line_1"
},
{
"field_type": "Text",
"id": "_rf5_T",
"name": "address_line_2"
},
{
"field_type": "Text",
"id": "_rf6_T",
"name": "city"
},
{
"field_type": "Text",
"id": "_rf7_T",
"name": "state_province_region"
},
{
"field_type": "Text",
"id": "_rf8_T",
"name": "postal_code"
},
{
"field_type": "Text",
"id": "_rf9_T",
"name": "country"
},
{
"field_type": "Text",
"id": "_rf10_T",
"name": "phone_number"
},
{
"field_type": "Text",
"id": "_rf11_T",
"name": "whatsapp"
},
{
"field_type": "Text",
"id": "_rf12_T",
"name": "line"
},
{
"field_type": "Text",
"id": "_rf13_T",
"name": "facebook"
},
{
"field_type": "Text",
"id": "_rf14_T",
"name": "unique_name"
},
{
"field_type": "Text",
"id": "_rf15_T",
"name": "email_domains",
"read_only": true
},
{
"field_type": "Date",
"id": "_rf16_D",
"name": "last_clicked",
"read_only": true
},
{
"field_type": "Date",
"id": "_rf17_D",
"name": "last_opened",
"read_only": true
},
{
"field_type": "Date",
"id": "_rf18_D",
"name": "last_emailed",
"read_only": true
},
{
"field_type": "Text",
"id": "_rf19_T",
"name": "singlesend_id",
"read_only": true
},
{
"field_type": "Text",
"id": "_rf20_T",
"name": "automation_id",
"read_only": true
},
{
"field_type": "Date",
"id": "_rf21_D",
"name": "created_at",
"read_only": true
},
{
"field_type": "Date",
"id": "_rf22_D",
"name": "updated_at",
"read_only": true
},
{
"field_type": "Text",
"id": "_rf23_T",
"name": "contact_id",
"read_only": true
}
]
}
}
},
"schema": {
"maxProperties": 120,
"minProperties": 0,
"properties": {
"_metadata": {
"$ref": "#/components/schemas/_metadata"
},
"custom_fields": {
"items": {
"$ref": "#/components/schemas/custom_field_definitions_response"
},
"type": "array"
},
"reserved_fields": {
"$ref": "#/components/schemas/reserved_field_definitions_response"
}
},
"required": [
"custom_fields",
"reserved_fields"
],
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Get All Field Definitions",
"tags": [
"Custom Fields"
],
"x-stoplight": {
"id": "GET_mc-field_definitions",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"post": {
"description": "**This endpoint creates a new custom field definition.**\n\nCustom field definitions are created with the given `name` and `field_type`. Although field names are stored in a case-sensitive manner, all field names must be case-insensitively unique. This means you may create a field named `CamelCase` or `camelcase`, but not both. Additionally, a Custom Field name cannot collide with any Reserved Field names. You should save the returned `id` value in order to update or delete the field at a later date. You can have up to 120 custom fields.\n\nThe custom field name should be created using only alphanumeric characters (A-Z and 0-9) and underscores (\\_). Custom fields can only begin with letters A-Z or underscores (_). The field type can be date, text, or number fields. The field type is important for creating segments from your contact database.\n\n**Note: Creating a custom field that begins with a number will cause issues with sending in Marketing Campaigns.**",
"operationId": "POST_mc-field_definitions",
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"field_type": "Text",
"name": "custom_field_name"
},
"properties": {
"field_type": {
"enum": [
"Text",
"Number",
"Date"
],
"type": "string"
},
"name": {
"maxLength": 100,
"minLength": 1,
"type": "string"
}
},
"required": [
"name",
"field_type"
],
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"_metadata": {
"self": "https://api.sendgrid.com/v3/marketing/field_definitions/a1_B"
},
"field_type": "Text",
"id": "a1_T",
"name": "custom_field_name"
}
}
},
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/custom_field_definitions_response"
},
{
"properties": {
"_metadata": {
"$ref": "#/components/schemas/_metadata"
}
},
"type": "object"
}
]
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"$ref": "#/components/schemas/error"
},
"type": "array",
"uniqueItems": true
}
},
"required": [
"errors"
],
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Create Custom Field Definition",
"tags": [
"Custom Fields"
],
"x-stoplight": {
"id": "POST_mc-field_definitions",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/marketing/field_definitions/{custom_field_id}": {
"delete": {
"description": "**This endpoint deletes a defined Custom Field.**\n\nYou cand delete only Custom Fields; Reserved Fields cannot be deleted.",
"operationId": "DELETE_mc-field_definitions-custom_field_id",
"responses": {
"204": {
"description": ""
},
"404": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"$ref": "#/components/schemas/error"
},
"type": "array",
"uniqueItems": true
}
},
"required": [
"errors"
],
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete Custom Field Definition",
"tags": [
"Custom Fields"
],
"x-stoplight": {
"id": "DELETE_mc-field_definitions-custom_field_id",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"in": "path",
"name": "custom_field_id",
"required": true,
"schema": {
"type": "string"
}
}
],
"patch": {
"description": "**This endopoint allows you to update a defined Custom Field.**\n\nOnly your Custom fields can be modified; Reserved Fields cannot be updated.",
"operationId": "PATCH_mc-field_definitions-custom_field_id",
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"name": "new_custom_field_name"
},
"properties": {
"name": {
"maxLength": 100,
"minLength": 1,
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"_metadata": {
"self": "https://api.sendgrid.com/v3/marketing/field_definitions/a1_B"
},
"field_type": "Text",
"id": "a1_T",
"name": "custom_field_name"
}
}
},
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/custom_field_definitions_response"
},
{
"properties": {
"_metadata": {
"$ref": "#/components/schemas/_metadata"
}
},
"type": "object"
}
]
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"$ref": "#/components/schemas/error"
},
"type": "array",
"uniqueItems": true
}
},
"required": [
"errors"
],
"type": "object"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"$ref": "#/components/schemas/error"
},
"type": "array",
"uniqueItems": true
}
},
"required": [
"errors"
],
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update Custom Field Definition",
"tags": [
"Custom Fields"
],
"x-stoplight": {
"id": "PATCH_mc-field_definitions-custom_field_id",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/marketing/lists": {
"get": {
"description": "**This endpoint returns an array of all of your contact lists.**",
"operationId": "GET_mc-lists",
"parameters": [
{
"description": "Maximum number of elements to return. Defaults to 100, returns 1000 max",
"in": "query",
"name": "page_size",
"required": false,
"schema": {
"default": 100,
"maximum": 1000,
"minimum": 1,
"type": "number"
}
},
{
"in": "query",
"name": "page_token",
"required": false,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"_metadata": {
"self": "https://api.sendgrid.com/v3/marketing/lists?page_size=100&page_token="
},
"result": [
{
"_metadata": {
"self": "https://api.sendgrid.com/v3/marketing/lists/abc12312-x3y4-1234-abcd-123qwe456rty"
},
"contact_count": 0,
"id": "abc12312-x3y4-1234-abcd-123qwe456rty",
"name": "list-name"
}
]
}
}
},
"schema": {
"properties": {
"_metadata": {
"$ref": "#/components/schemas/metadata"
},
"result": {
"items": {
"$ref": "#/components/schemas/list"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Get All Lists",
"tags": [
"Lists"
],
"x-stoplight": {
"id": "GET_mc-lists",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"post": {
"description": "**This endpoint creates a new contacts list.**\n\nOnce you create a list, you can use the UI to [trigger an automation](https://sendgrid.com/docs/ui/sending-email/getting-started-with-automation/#create-an-automation) every time you add a new contact to the list.\n\nA link to the newly created object is in `_metadata`.",
"operationId": "POST_mc-lists",
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"name": "list-name"
},
"properties": {
"name": {
"description": "Your name for your list",
"maxLength": 100,
"minLength": 1,
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"_metadata": {
"self": "https://api.sendgrid.com/v3/marketing/lists/ca7a3796-e8a8-4029-9ccb-df8937940562"
},
"contact_count": 0,
"id": "ca7a3796-e8a8-4029-9ccb-df8937940562",
"name": "list-name"
}
}
},
"schema": {
"$ref": "#/components/schemas/list"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"$ref": "#/components/schemas/error"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Create List",
"tags": [
"Lists"
],
"x-stoplight": {
"id": "POST_mc-lists",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/marketing/lists/{id}": {
"delete": {
"description": "**This endpoint allows you to deletes a specific list.**\n\nOptionally, you can also delete contacts associated to the list. The query parameter, `delete_contacts=true`, will delete the list and start an asynchronous job to delete associated contacts.",
"operationId": "DELETE_lists-id",
"parameters": [
{
"description": "Flag indicates that all contacts on the list are also to be deleted.",
"in": "query",
"name": "delete_contacts",
"required": false,
"schema": {
"default": false,
"type": "boolean"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"job_id": "abc12312-x3y4-1234-abcd-123qwe456rty"
}
}
},
"schema": {
"description": "The delete has been accepted and is processing.",
"properties": {
"job_id": {
"description": "job_id of the async job",
"type": "string"
}
},
"type": "object"
}
}
},
"description": ""
},
"204": {
"content": {
"application/json": {
"schema": {
"description": "The delete has been processed. ",
"type": "string"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"type": "object"
},
"type": "array"
}
},
"required": [
"errors"
],
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete a list",
"tags": [
"Lists"
],
"x-stoplight": {
"id": "DELETE_lists-id",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"get": {
"description": "**This endpoint returns data about a specific list.**\n\nSetting the optional parameter `contact_sample=true` returns the `contact_sample` in the response body. Up to fifty of the most recent contacts uploaded or attached to a list will be returned, sorted alphabetically, by email address.\n\nThe full contact count is also returned.",
"operationId": "GET_mc-lists-id",
"parameters": [
{
"description": "Setting this parameter to the true will cause the contact_sample to be returned",
"in": "query",
"name": "contact_sample",
"schema": {
"default": false,
"type": "boolean"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"_metadata": {
"self": "https://api.sendgrid.com/v3/marketing/lists/199abb98-0af3-4a8d-b371-6811ff85d062"
},
"contact_count": 2,
"contact_sample": {
"created_at": "2620-06-16T17:03:54.867Z",
"id": "c3445f88-5c69-4237-86e6-9ec9de958050",
"list_ids": [
"199abb98-0af3-4a8d-b371-6811ff85d062"
],
"updated_at": "4780-12-06T06:51:30.024Z"
},
"id": "199abb98-0af3-4a8d-b371-6811ff85d062",
"name": "list-name"
}
}
},
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/list"
},
{
"properties": {
"contact_sample": {
"$ref": "#/components/schemas/contact-details2"
}
},
"type": "object"
}
]
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/error"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Get a List by ID",
"tags": [
"Lists"
],
"x-stoplight": {
"id": "GET_mc-lists-id",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"patch": {
"description": "**This endpoint updates the name of a list.**",
"operationId": "PATCH_mc-lists-id",
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"name": "updated-list-name"
},
"properties": {
"name": {
"description": "Your name for your list.",
"type": "string"
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"_metadata": {
"self": "https://api.sendgrid.com/v3/marketing/lists/abc12312-x3y4-1234-abcd-123qwe456rty"
},
"contact_count": 0,
"id": "abc12312-x3y4-1234-abcd-123qwe456rty",
"name": "updated-list-name"
}
}
},
"schema": {
"$ref": "#/components/schemas/list"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"$ref": "#/components/schemas/error"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update List",
"tags": [
"Lists"
],
"x-stoplight": {
"id": "PATCH_mc-lists-id",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/marketing/lists/{id}/contacts": {
"delete": {
"description": "**This endpoint allows you to remove contacts from a given list.**\n\nThe contacts will not be deleted. Only their list membership will be changed.",
"operationId": "DELETE_mc-lists-id-contacts",
"parameters": [
{
"description": "comma separated list of contact ids",
"in": "query",
"name": "contact_ids",
"required": true,
"schema": {
"minLength": 1,
"type": "string"
}
}
],
"responses": {
"202": {
"content": {
"application/json": {
"schema": {
"description": "The removal is accepted and processing.",
"properties": {
"job_id": {
"description": "job_id of the async job",
"type": "string"
}
},
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"schema": {
"description": "If the specified list id does not exist. If the specified contact does not exist."
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Remove Contacts from a List",
"tags": [
"Lists"
],
"x-stoplight": {
"id": "DELETE_mc-lists-id-contacts",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
]
},
"/marketing/lists/{id}/contacts/count": {
"get": {
"description": "**This endpoint returns the number of contacts on a specific list.**",
"operationId": "GET_mc-lists-id-contacts-count",
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"billable_count:": 0,
"contact_count": 0
}
}
},
"schema": {
"properties": {
"billable_count": {
"type": "integer"
},
"contact_count": {
"type": "integer"
}
},
"type": "object"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Get List Contact Count",
"tags": [
"Lists"
],
"x-stoplight": {
"id": "GET_mc-lists-id-contacts-count",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
]
},
"/marketing/segments": {
"get": {
"description": "**This endpoint allows you to retrieve a list of segments.**\n\nThe query param `parent_list_ids` is treated as a filter. Any match will be returned. 0 matches will return a response code of 200 with an empty `results` array.\n\n`parent_list_ids` | `no_parent_list_id` | `result`\n-----------------:|:--------------------:|:-------------\nempty | false | all segments\nvalues | false | segments filtered by list_ids\nvalues | true | segments filtered by list_ids and segments with no parent list_ids\nempty | true | segments with no parent list_ids",
"operationId": "GET_marketing-segments",
"parameters": [
{
"description": "A comma separated list of list ids to be used when searching for segments with the specified parent_list_id, no more than 50 is allowed",
"in": "query",
"name": "parent_list_ids",
"schema": {
"type": "string"
}
},
{
"description": "If set to `true` segments with an empty value of `parent_list_id` will be returned in the filter. If the value is not present it defaults to 'false'.",
"in": "query",
"name": "no_parent_list_id",
"schema": {
"default": false,
"type": "boolean"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"results": [
{
"contacts_count": 78434802,
"created_at": "2921-01-27T19:33:36.479Z",
"id": "12099613-91e5-4d09-a900-df7626325288",
"sample_updated_at": "4685-11-26T07:05:04.660Z",
"updated_at": "2883-07-10T13:13:37.697Z"
},
{
"contacts_count": 34177253,
"created_at": "2575-07-26T23:17:20.984Z",
"id": "60c37015-3734-4c8e-b293-68cfa2ae4fa5",
"name": "amet",
"parent_list_id": "fd38af3d-3f69-4947-8dca-5fa967e7be82",
"sample_updated_at": "3074-09-04T09:49:58.127Z",
"updated_at": "5116-10-15T07:37:40.838Z"
}
]
}
}
},
"schema": {
"properties": {
"results": {
"items": {
"$ref": "#/components/schemas/segment_summary"
},
"minItems": 0,
"type": "array",
"uniqueItems": true
}
},
"required": [
"results"
],
"type": "object"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"message": {
"type": "string"
}
},
"required": [
"message"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"errors"
],
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Get List of Segments",
"tags": [
"segmenting contacts",
"Segmenting Contacts"
],
"x-stoplight": {
"id": "GET_marketing-segments",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"post": {
"description": "**This endpoint allows you to create a segment.**",
"operationId": "POST_marketing-segments",
"requestBody": {
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/segment_write_v2"
},
{
"properties": {
"parent_list_id": {
"description": "The id of the list if this segment is a child of a list. This implies the query is rewritten as `(${query_dsl}) AND CONTAINS(list_ids, ${parent_list_id})`",
"format": "uuid",
"maxLength": 36,
"minLength": 36,
"type": "string"
}
},
"type": "object"
}
]
}
}
}
},
"responses": {
"201": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"contact_summary": {
"address_line_1": "ut sunt Duis eu",
"address_line_2": "in culpa esse non",
"city": "ÔXƫɋƄř",
"contact_id": "1a541ca7-9fef-42c8-8947-f3f8a3b33ffe",
"country": "eiusmod",
"custom_fields": {
"custom_field_name1": "est mollit officia adipisicing dolo",
"custom_field_name2": "dolore cillum"
},
"first_name": "exercitation Duis nisi",
"last_name": "aut",
"list_ids": [
"c856a255-12f1-4b55-8564-218fd7eb34a7",
"130c8813-0d6f-4b9e-b154-736bb9db2ff8",
"c245021d-4444-4086-a498-3ffee7fa8cdf"
],
"postal_code": -88086402,
"primary_email": "D8OsYF5ok@YtX.kcg",
"state_province_region": "consequat culpa in"
},
"contacts_count": 0,
"contacts_sample": [
{
"address_line_1": "in occaecat labore est tempor",
"address_line_2": "magna adipisicing",
"alternate_emails": [
"dTeJZgU5uN9UYSo@nfIB.ijxg"
],
"city": "ƞó",
"country": "voluptate in in reprehenderit aliquip",
"custom_fields": {
"custom_field_name1": "amet deserunt mollit",
"custom_field_name2": "minim consequat id"
},
"email": "KLTF@SurgGzlAxCPOqhOUHYNBLsfpfE.trh",
"first_name": "ullamco esse culpa do",
"id": "e70eac25-1431-4231-bccd-1cfab432301e",
"last_name": "officia laboris veniam consequat",
"postal_code": -75218567,
"state_province_region": "culpa ut"
},
{
"address_line_1": "labore",
"address_line_2": "non",
"alternate_emails": [
"gQol@Xcfilli.hc",
"n4K7OdaVQh@YfsnF.ie",
"TdnvS3nMStREn@miFjGzNDCPZWhiswJNxrFnOYdUAZEpesQ.yxpu",
"xRzGDTTzzbYK@eJ.wpgb",
"iI1rOpx2ct@aZhuYGZBxJLZ.phr"
],
"city": "ĔȸąÂ¸ȠɏbɄ",
"country": "do",
"custom_fields": {
"custom_field_name1": "amet deserunt mollit",
"custom_field_name2": "minim consequat id"
},
"email": "t7N5TjDmKhC0@gfdifW.ua",
"first_name": "ea et eu",
"id": "db637d33-bce1-462c-ae9c-91ec4f761de6",
"last_name": "velit Ut laborum ipsu",
"list_ids": [
"c712288b-2300-4069-bef4-2e05b5948ec3",
"9003ef29-5eb7-4951-898b-1b102e490d6e"
],
"postal_code": -95171713,
"state_province_region": "deserunt dolore"
}
],
"created_at": "2921-01-27T19:33:36.479Z",
"id": "864feb2e-5e93-47bf-b63e-21746c988105",
"name": "aute amet deserunt adipisicing",
"next_sample_update": "culpa sit occaecat fugiat quis",
"query_dsl": "email LIKE %twilio.com",
"sample_updated_at": "3407-09-25T04:25:02.140Z",
"updated_at": "4389-06-21T16:59:51.403Z"
}
}
},
"schema": {
"$ref": "#/components/schemas/full-segment"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"message": {
"type": "string"
}
},
"required": [
"message"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"errors"
],
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Create Segment",
"tags": [
"segmenting contacts",
"Segmenting Contacts"
],
"x-stoplight": {
"id": "POST_marketing-segments",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 201
},
"public": true
}
}
},
"/marketing/segments/2.0": {
"get": {
"description": "The query param `parent_list_ids` is treated as a filter. Any match will be returned. 0 matches will return a response code of 200 with an empty `results` array.\n\n`parent_list_ids` | `no_parent_list_id` | `result`\n-----------------:|:--------------------:|:-------------\nempty | false | all segments\nvalues | false | segments filtered by list_ids\nvalues | true | segments filtered by list_ids and segments with no parent list_ids\nempty | true | segments with no parent list_ids",
"operationId": "GET_segments",
"parameters": [
{
"description": "A comma separated list up to 50 in size, to filter segments on. Only segments that have any of these list ids as the parent list will be retrieved. This is different from the parameter of the same name used when creating a segment.",
"in": "query",
"name": "parent_list_ids",
"required": false,
"schema": {
"type": "string"
}
},
{
"description": "If set to `true` segments with an empty value of `parent_list_id` will be returned in the filter. If the value is not present it defaults to 'false'.",
"in": "query",
"name": "no_parent_list_id",
"required": false,
"schema": {
"default": false,
"type": "boolean"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/all_segments_response"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/errors-seg-v2"
}
}
},
"description": ""
},
"404": {
"description": ""
},
"500": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/errors-seg-v2"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Get List of Segments",
"tags": [
"Segmenting Contacts V2"
],
"x-stoplight": {
"id": "GET_segments",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"post": {
"description": "Segment `name` has to be unique. A user can not create a new segment with an existing segment name.",
"operationId": "POST_segments",
"requestBody": {
"$ref": "#/components/requestBodies/segment_write_v2"
},
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/segment_response"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/errors-seg-v2"
}
}
},
"description": ""
},
"404": {
"description": ""
},
"429": {
"description": ""
},
"500": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/errors-seg-v2"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Create Segment",
"tags": [
"Segmenting Contacts V2"
],
"x-stoplight": {
"id": "POST_segments",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/marketing/segments/2.0/{segment_id}": {
"delete": {
"description": "**The Segmentation V2 API is currently in private beta. If you'd like to be added to the beta, please fill out this [form](https://docs.google.com/forms/d/e/1FAIpQLSd5zwC9dRk8lAp1oTWjdGc-aSY69flW_7wnutvKBhpUluSnfQ/viewform)**",
"operationId": "DELETE_segments-segment_id",
"responses": {
"202": {
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/errors-seg-v2"
}
}
},
"description": ""
},
"404": {
"description": ""
},
"500": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/errors-seg-v2"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete segment",
"tags": [
"Segmenting Contacts V2 - Beta"
],
"x-stoplight": {
"id": "DELETE_segments-segment_id",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"get": {
"description": "",
"operationId": "GET_segments-segment_id",
"parameters": [
{
"description": "Defaults to `true`. Set to `false` to exclude the contacts_sample in the response.",
"in": "query",
"name": "contacts_sample",
"schema": {
"type": "boolean"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/segment_response"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/errors-seg-v2"
}
}
},
"description": ""
},
"500": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/errors-seg-v2"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Get Segment by ID",
"tags": [
"Segmenting Contacts V2"
],
"x-stoplight": {
"id": "GET_segments-segment_id",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"in": "path",
"name": "segment_id",
"required": true,
"schema": {
"type": "string"
}
}
],
"patch": {
"description": "Segment `name` has to be unique. A user can not create a new segment with an existing segment name.",
"operationId": "PATCH_segments-segment_id",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/segment_update"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/segment_response"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/errors-seg-v2"
}
}
},
"description": ""
},
"429": {
"description": ""
},
"500": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/errors-seg-v2"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update Segment",
"tags": [
"Segmenting Contacts V2"
],
"x-stoplight": {
"id": "PATCH_segments-segment_id",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/marketing/segments/delete": {
"post": {
"description": "This endpoint allows you to delete segments in bulk.\n\nIf the segments are used by automations or the segments do not exist in the database, the segment IDs that could not be deleted along with automation IDs that are associated to those segments will be returned.",
"operationId": "POST_marketing-segments-delete",
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"ids": [
"a14dcc63-d651-4c57-9826-4a3705f5c78d",
"f3de551e-dc5c-4d42-bd08-c7f87f87f0e8",
"1b8107b5-adf4-401c-8865-fa84ba178fb9",
"d7900715-c904-4728-acff-9ab79627579e",
"16641f5b-cfa3-41b9-9626-244488ee85b1"
]
},
"properties": {
"ids": {
"items": {
"format": "uuid",
"type": "string"
},
"type": "array"
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error": {
"description": "error message that indicates why segment cannot be deleted (\"in-use\", \"segment not found\", \"invalid uuid\")",
"type": "string"
},
"id": {
"description": "Segment ID",
"type": "string"
},
"resources": {
"description": "resources in which segment is being used",
"properties": {
"ids": {
"description": "the resource ids",
"items": {
"type": "string"
},
"type": "array"
},
"type": {
"description": "the type of resource in use (e.g., \"automation\")",
"type": "string"
}
},
"type": "object"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"202": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Bulk Delete Segments",
"tags": [
"Segmenting Contacts"
],
"x-stoplight": {
"id": "POST_marketing-segments-delete",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": false
}
}
},
"/marketing/segments/{segment_id}": {
"delete": {
"description": "**This endpoint allows you to delete a segment by `segment_id`.**\n\nNote that deleting a segment does not delete the contacts associated with the segment by default. Contacts associated with a deleted segment will remain in your list of all contacts and any other segments they belong to.",
"operationId": "DELETE_marketing-segments-segment_id",
"responses": {
"202": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"message",
"field"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"errors"
],
"type": "object"
}
}
},
"description": ""
},
"500": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"message": {
"type": "string"
}
},
"required": [
"message"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"errors"
],
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete Segment",
"tags": [
"segmenting contacts",
"Segmenting Contacts"
],
"x-stoplight": {
"id": "DELETE_marketing-segments-segmentid",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 202
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve a single segment by ID.**",
"operationId": "GET_marketing-segments-segment_id",
"parameters": [
{
"description": "Defaults to `false`. Set to `true` to return the parsed SQL AST as a JSON object in the field `query_json`",
"in": "query",
"name": "query_json",
"schema": {
"type": "boolean"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"contacts_count": -83213117,
"contacts_sample": [
{
"address_line_1": "in occaecat labore est tempor",
"address_line_2": "magna adipisicing",
"alternate_emails": [
"dTeJZgU5uN9UYSo@nfIB.ijxg"
],
"city": "ƞó",
"country": "voluptate in in reprehenderit aliquip",
"custom_fields": {
"custom_field_name1": "amet deserunt mollit",
"custom_field_name2": "minim consequat id"
},
"email": "KLTF@SurgGzlAxCPOqhOUHYNBLsfpfE.trh",
"first_name": "ullamco esse culpa do",
"id": "e70eac25-1431-4231-bccd-1cfab432301e",
"last_name": "officia laboris veniam consequat",
"postal_code": -75218567,
"state_province_region": "culpa ut"
},
{
"address_line_1": "labore",
"address_line_2": "non",
"alternate_emails": [
"gQol@Xcfilli.hc",
"n4K7OdaVQh@YfsnF.ie",
"TdnvS3nMStREn@miFjGzNDCPZWhiswJNxrFnOYdUAZEpesQ.yxpu",
"xRzGDTTzzbYK@eJ.wpgb",
"iI1rOpx2ct@aZhuYGZBxJLZ.phr"
],
"city": "ĔȸąÂ¸ȠɏbɄ",
"country": "do",
"custom_fields": {
"custom_field_name1": "amet deserunt mollit",
"custom_field_name2": "minim consequat id"
},
"email": "t7N5TjDmKhC0@gfdifW.ua",
"first_name": "ea et eu",
"id": "db637d33-bce1-462c-ae9c-91ec4f761de6",
"last_name": "velit Ut laborum ipsu",
"list_ids": [
"c712288b-2300-4069-bef4-2e05b5948ec3",
"9003ef29-5eb7-4951-898b-1b102e490d6e"
],
"postal_code": -95171713,
"state_province_region": "deserunt dolore"
}
],
"created_at": "2921-01-27T19:33:36.479Z",
"id": "3b049926-0a54-4a91-83f0-086ace63c530",
"name": "enim et anim",
"query_dsl": "nostrud",
"sample_updated_at": "3407-09-25T04:25:02.140Z",
"updated_at": "4389-06-21T16:59:51.403Z"
}
}
},
"schema": {
"$ref": "#/components/schemas/full-segment"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"message",
"field"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"errors"
],
"type": "object"
}
}
},
"description": ""
},
"500": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"message": {
"type": "string"
}
},
"required": [
"message"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"errors"
],
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Get Segment by ID",
"tags": [
"segmenting contacts",
"Segmenting Contacts"
],
"x-stoplight": {
"id": "GET_marketing-segments-segmentid",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"in": "path",
"name": "segment_id",
"required": true,
"schema": {
"format": "uuid",
"maxLength": 36,
"minLength": 36,
"type": "string"
}
}
],
"patch": {
"description": "**This endpoint allows you to update a segment.**\n\nSegment `name` needs to be unique. A user can not update a segment name to an existing one.",
"operationId": "PATCH_marketing-segments-segment_id",
"requestBody": {
"$ref": "#/components/requestBodies/segment_write_v2"
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"contacts_count": -35493018,
"contacts_sample": [
{
"address_line_1": "deserunt cillum aliqua nostrud ullamco",
"address_line_2": "sint",
"alternate_emails": [
"qXP-RD97fLl@oEDaUynqnNRHJHdoJAWVGXwvI.qlv",
"W0ngFWmR@WcQuSbPFVPZvLrjCFadfimFi.eqf",
"mYBC0ea5UxFI@qwO.jh",
"Nhf1OmY@KCSjTQsXYpbKrBUsQFHrnLuY.oef"
],
"city": "ŷ(ç",
"country": "consectetur quis voluptate",
"custom_fields": {
"custom_field_name1": "dolor aute irure Excepteur"
},
"email": "exmS@KIzibBSmaUUHQa.uvv",
"first_name": "consequat nulla in",
"id": "4cff9fdf-1bee-44f7-95dc-a101f9ed3cfe",
"last_name": "irure nostrud elit eu",
"list_ids": [
"f9d5987d-7a01-4a66-b77e-1f08a392304b",
"b4e3b028-01d4-428b-9ef5-24bcd90fa02c",
"fedab84f-9aa5-449d-99e2-7b1361f8ee61"
],
"postal_code": 62721676,
"state_province_region": "minim"
},
{
"address_line_1": "culpa eu eiusmod sint",
"address_line_2": "sed velit sint",
"alternate_emails": [
"Zs6vnQbMU@XTamDsXEGJWBMMEacOc.hitv",
"Epl@ySBrQCFJZnjggkAYLu.ppta"
],
"city": "BĊJ",
"country": "in laboris Excepteur consequat",
"custom_fields": {
"custom_field_name1": "esse magna Ut",
"custom_field_name2": "culpa Excepteur"
},
"email": "atNeYGC4nbF42@VOCUWuGaYr.ystm",
"first_name": "est",
"id": "093a66b8-bca8-4d8a-b32a-091d939c1928",
"last_name": "in",
"postal_code": 33736880,
"state_province_region": "Lorem Ut aliqua elit"
},
{
"address_line_1": "occaecat aute enim",
"address_line_2": "ipsum quis in",
"alternate_emails": [
"S8u@ZVGjHsXdSWtlhhad.ximc",
"GA1MN72v3uA@MPDvMUmTYjwFCsEaGnFnvVzJVqUTl.ehws"
],
"city": "ɌſĕĝȤ¶Ǖ",
"country": "occaecat veniam",
"custom_fields": {
"custom_field_name1": "dolor aute irure Excepteur"
},
"email": "Jx660QHClqRrC@SavQvcdRpJlLqepwoEUCm.ar",
"first_name": "sed eu veli",
"id": "08939f9c-2c87-4639-bd07-16d41f90a5eb",
"last_name": "aliqua sit culpa",
"postal_code": -66445052,
"state_province_region": "ut nisi sed esse"
},
{
"address_line_1": "et incididunt",
"address_line_2": "veniam quis non exercitation Duis",
"alternate_emails": [
"F5WhHCA3oL6Ix@wOKzwIsvHbFi.mrlc"
],
"city": "DzƐȹŲdž",
"country": "reprehenderit",
"custom_fields": {
"custom_field_name1": "dolor aute irure Excepteur"
},
"email": "AnoFtJq@IolRDmfj.njt",
"first_name": "do mollit velit",
"id": "0667ed97-7b7b-44aa-a115-0301067660d7",
"last_name": "voluptate dolor",
"list_ids": [
"ffd225a8-2008-4457-87af-1cffff5b1ccb"
],
"postal_code": -19694583,
"state_province_region": "occaecat"
},
{
"address_line_1": "dolor",
"address_line_2": "elit ex labore sunt aliquip",
"alternate_emails": [
"ksqbx6BInlB@ouWBjjxwFBwVQjdnEKXy.xi",
"TAe7F2m8dFlF@SirYykzbe.aj",
"T9c2Y@HjVQY.zz",
"p7ShfET@vMMnTUqoqngPSEqbpyoeWN.jlqn",
"0VJSIhIUT2k@mJXVtdZVKKswW.xoca"
],
"city": "ÝǘźƝǝƉƝ",
"country": "id sunt nisi",
"custom_fields": {
"custom_field_name1": "amet",
"custom_field_name2": "enim quis"
},
"email": "kF4@gYYdAxzetJrWszLAHuBUTu.rzq",
"first_name": "irure laboris minim",
"id": "449767ca-d446-45f1-aa9b-432f441b5ca1",
"last_name": "id nostrud aliqua sit",
"list_ids": [
"2870627c-b9ea-4dcf-bde0-36c3e0e3eca9",
"575d86aa-4dcc-4b7d-b7de-ded856913198",
"fb82a17f-5777-439d-9b8c-2c565c8ddf20"
],
"postal_code": 60466925,
"state_province_region": "nostrud Duis non nulla laborum"
}
],
"created_at": "2014-12-23T17:18:52.907Z",
"id": "5fff6250-b766-4959-a183-2e1fa565c4ce",
"name": "List Name",
"query_dsl": "Email LIKE %twilio.com",
"sample_updated_at": "2146-04-13T16:46:32.328Z",
"updated_at": "4389-06-21T16:59:51.403Z"
}
}
},
"schema": {
"$ref": "#/components/schemas/full-segment"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"message",
"field"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"errors"
],
"type": "object"
}
}
},
"description": ""
},
"500": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"message": {
"type": "string"
}
},
"required": [
"message"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"errors"
],
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update Segment",
"tags": [
"segmenting contacts",
"Segmenting Contacts"
],
"x-stoplight": {
"id": "PATCH_marketing-segments-segmentid",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/marketing/senders": {
"post": {
"description": "**This endpoint allows you to create a new sender identity.**\n\n*You may create up to 100 unique sender identities.*\n\nSender identities are required to be verified before use. If your domain has been authenticated, a new sender identity will auto verify on creation. Otherwise an email will be sent to the `from.email`.",
"operationId": "POST_marketing-senders",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"address": "1234 Fake St.",
"address_2": "",
"city": "San Francisco",
"country": "United States",
"from": {
"email": "orders@example.com",
"name": "Example Orders"
},
"nickname": "Example Orders",
"reply_to": {
"email": "support@example.com",
"name": "Example Support"
},
"state": "CA",
"zip": "94105"
},
"properties": {
"address": {
"description": "The physical address of the sender identity.",
"type": "string"
},
"address_2": {
"description": "Additional sender identity address information.",
"type": "string"
},
"city": {
"description": "The city of the sender identity.",
"type": "string"
},
"country": {
"description": "The country of the sender identity.",
"type": "string"
},
"from": {
"properties": {
"email": {
"description": "This is where the email will appear to originate from for your recipient",
"type": "string"
},
"name": {
"description": "This is the name appended to the from email field. IE - Your name or company name.",
"type": "string"
}
},
"required": [
"email",
"name"
],
"type": "object"
},
"nickname": {
"description": "A nickname for the sender identity. Not used for sending.",
"type": "string"
},
"reply_to": {
"properties": {
"email": {
"description": "This is the email that your recipient will reply to.",
"type": "string"
},
"name": {
"description": "This is the name appended to the reply to email field. IE - Your name or company name.",
"type": "string"
}
},
"required": [
"email"
],
"type": "object"
},
"state": {
"description": "The state of the sender identity.",
"type": "string"
},
"zip": {
"description": "The zipcode of the sender identity.",
"type": "string"
}
},
"required": [
"nickname",
"from",
"address",
"city",
"country"
],
"type": "object"
}
}
}
},
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/senderID"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Create a Sender Identity",
"tags": [
"Senders"
],
"x-stoplight": {
"id": "POST_marketing-senders",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/marketing/singlesends": {
"delete": {
"description": "**This endpoint allows you to delete multiple Single Sends using an array of Single Sends IDs.**\n\nTo first retrieve all your Single Sends' IDs, you can make a GET request to the `/marketing/singlensends` endpoint.\n\nPlease note that a DELETE request is permanent, and your Single Sends will not be recoverable after deletion.",
"operationId": "DELETE_marketing-singlesends",
"parameters": [
{
"description": "Single Send IDs to delete",
"explode": false,
"in": "query",
"name": "ids",
"schema": {
"items": {
"type": "string"
},
"maxItems": 50,
"minItems": 1,
"type": "array"
},
"style": "form"
}
],
"responses": {
"204": {
"description": ""
},
"404": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"type": "string"
},
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"500": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"type": "string"
},
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Bulk Delete Single Sends",
"tags": [
"Single Sends"
],
"x-stoplight": {
"id": "DELETE_marketing-singlesends",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve all your Single Sends.**\n\nReturns all of your Single Sends with condensed details about each, including the Single Sends' IDs. For more details about an individual Single Send, pass the Single Send's ID to the `/marketing/singlesends/{id}` endpoint.",
"operationId": "GET_marketing-singlesends",
"parameters": [
{
"in": "query",
"name": "page_size",
"schema": {
"type": "integer"
}
},
{
"in": "query",
"name": "page_token",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"properties": {
"_metadata": {
"$ref": "#/components/schemas/_metadata"
},
"result": {
"items": {
"$ref": "#/components/schemas/singlesend_response_short"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"500": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"type": "string"
},
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Get All Single Sends",
"tags": [
"Single Sends"
],
"x-stoplight": {
"id": "GET_marketing-singlesends",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"post": {
"description": "**This endpoint allows you to create a new Single Send.**\n\nPlease note that if you are migrating from the previous version of Single Sends, you no longer need to pass a template ID with your request to this endpoint. Instead, you will pass all template data in the `email_config` object.",
"operationId": "POST_marketing-singlesends",
"requestBody": {
"$ref": "#/components/requestBodies/singlesend_request"
},
"responses": {
"201": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"categories": [
"unique opens"
],
"created_at": "2020-05-18T17:28:27.272Z",
"email_config": {
"custom_unsubscribe_url": null,
"editor": "code",
"generate_plain_content": true,
"html_content": "",
"ip_pool": null,
"plain_content": "",
"sender_id": null,
"subject": "",
"suppression_group_id": null
},
"id": "27c21bbf-a12c-440b-b8bf-c526975328ca",
"name": "Example API Created Single Send",
"send_at": "2020-06-16T00:19:55.106Z",
"send_to": {
"list_ids": [
"f2fe66a1-43f3-4e3a-87b1-c6a600d805f0"
]
},
"status": "scheduled"
}
}
},
"schema": {
"$ref": "#/components/schemas/singlesend_response"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"type": "string"
},
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"500": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"type": "string"
},
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Create Single Send",
"tags": [
"Single Sends"
],
"x-stoplight": {
"id": "POST_marketing-singlesends",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/marketing/singlesends/categories": {
"get": {
"description": "**This endpoint allows you to retrieve all the categories associated with your Single Sends.**\n\nThis endpoint will return your latest 1,000 categories.",
"operationId": "GET_marketing-singlesends-categories",
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"categories": [
"equipment",
"shoes",
"sports"
]
}
}
},
"schema": {
"properties": {
"categories": {
"description": "list of latest one thousand unique categories associated with all Single Sends in ascending order",
"items": {
"type": "string"
},
"maxItems": 1000,
"type": "array",
"uniqueItems": true
}
},
"type": "object"
}
}
},
"description": ""
},
"500": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"type": "string"
},
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Get All Categories",
"tags": [
"Single Sends"
],
"x-stoplight": {
"id": "GET_marketing-singlesends-categories",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/marketing/singlesends/search": {
"post": {
"description": "**This endpoint allows you to search for Single Sends based on specified criteria.**\n\nYou can search for Single Sends by passing a combination of values using the `name`, `status`, and `categories` request body fields.\n\nFor example, if you want to search for all Single Sends that are \"drafts\" or \"scheduled\" and also associated with the category \"shoes,\" your request body may look like the example below.\n\n```javascript\n{\n \"status\": [\n \"draft\",\n \"scheduled\"\n ],\n \"categories\": [\n \"shoes\"\n ],\n}\n```",
"operationId": "POST_marketing-singlesends-search",
"parameters": [
{
"in": "query",
"name": "page_size",
"schema": {
"type": "integer"
}
},
{
"in": "query",
"name": "page_token",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/singlesend_search"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"_metadata": {
"count": 1,
"next": "https://a/DYEsTUDww9-",
"prev": "https://a/P0Enoayd",
"self": "https://a/nwNSrPSWt7d"
},
"result": [
{
"abtest": null,
"categories": [
"shoes"
],
"created_at": "4739-10-29T07:11:32.476Z",
"id": "df25ffdf-6a96-458a-9419-6d87d3094c6b",
"is_abtest": true,
"name": "single-send-1",
"send_at": "2471-05-31T15:46:18.797Z",
"status": "triggered",
"updated_at": "3263-04-09T09:05:08.193Z"
}
]
}
}
},
"schema": {
"properties": {
"_metadata": {
"$ref": "#/components/schemas/_metadata"
},
"result": {
"items": {
"$ref": "#/components/schemas/singlesend_response_short"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"type": "string"
},
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Get Single Sends Search",
"tags": [
"Single Sends"
],
"x-stoplight": {
"id": "POST_marketing-singlesends-search",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/marketing/singlesends/{id}": {
"delete": {
"description": "**This endpoint allows you to delete one Single Send using a Single Send ID.**\n\nTo first retrieve all your Single Sends' IDs, you can make a GET request to the `/marketing/singlensends` endpoint.\n\nPlease note that a `DELETE` request is permanent, and your Single Send will not be recoverable after deletion.",
"operationId": "DELETE_marketing-singlesends-id",
"responses": {
"204": {
"description": ""
},
"404": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"type": "string"
},
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"500": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"type": "string"
},
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete Single Send by ID",
"tags": [
"Single Sends"
],
"x-stoplight": {
"id": "DELETE_marketing-singlesends-id",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve details about one Single Send using a Single Send ID.**\n\nYou can retrieve all of your Single Sends by making a GET request to the `/marketing/singlesends` endpoint.",
"operationId": "GET_marketing-singlesends-id",
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"created_at": "2020-12-13T16:24:42.013Z",
"id": "2f6dec81-43b9-4c67-a890-3a38cb63b54a",
"name": "single-send-1",
"send_to": {
"all": true,
"segment_ids": [
"dad84de3-bec4-4e04-b132-2cbfd4bb3789",
"7dce758d-1155-4102-88d2-ca65565ac98b"
]
},
"status": "scheduled"
}
}
},
"schema": {
"$ref": "#/components/schemas/singlesend_response"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"type": "string"
},
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"500": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"type": "string"
},
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Get Single Send by ID",
"tags": [
"Single Sends"
],
"x-stoplight": {
"id": "GET_marketing-singlesends-id",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"patch": {
"description": "**This endpoint allows you to update a Single Send using a Single Send ID.**\n\nYou only need to pass the fields you want to update. Any blank/missing fields will remain unaltered.",
"operationId": "PATCH_marketing-singlesends-id",
"requestBody": {
"$ref": "#/components/requestBodies/singlesend_request"
},
"responses": {
"202": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/singlesend_response"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"type": "string"
},
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"type": "string"
},
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"500": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"type": "string"
},
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update Single Send",
"tags": [
"Single Sends"
],
"x-stoplight": {
"id": "PATCH_marketing-singlesends-id",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"post": {
"description": "**This endpoint allows you to duplicate an existing Single Send using its Single Send ID.**\n\nDuplicating a Single Send is useful when you want to create a Single Send but don't want to start from scratch. Once duplicated, you can update or edit the Single Send by making a PATCH request to the `/marketing/singlesends/{id}` endpoint.\n \nIf you leave the `name` field blank, your duplicate will be assigned the name of the Single Send it was copied from with the text “Copy of ” prepended to it. The `name` field length is limited to 100 characters, so the end of the new Single Send name, including “Copy of ”, will be trimmed if the name exceeds this limit.",
"operationId": "POST_marketing-singlesends-id",
"requestBody": {
"content": {
"application/json": {
"schema": {
"properties": {
"name": {
"description": "The name of the duplicate Single Send. If you choose to leave the name field blank, your duplicate will be assigned the name of the Single Send it was copied from with the text 'Copy of ' prepended to it. The end of the new Single Send name, including 'Copy of ', will be trimmed if the name exceeds the character limit.",
"maxLength": 100,
"minLength": 1,
"type": "string"
}
},
"type": "object"
}
}
}
},
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/singlesend_response"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"type": "string"
},
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"500": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"type": "string"
},
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Duplicate Single Send",
"tags": [
"Single Sends"
],
"x-stoplight": {
"id": "POST_marketing-singlesends-id",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/marketing/singlesends/{id}/schedule": {
"delete": {
"description": "**This endpoint allows you to cancel a scheduled Single Send using a Single Send ID.**\n\nMaking a DELETE request to this endpoint will cancel the scheduled sending of a Single Send. The request will not delete the Single Send itself. Deleting a Single Send can be done by passing a DELETE request to `/marketing/singlesends/{id}`.",
"operationId": "DELETE_marketing-singlesends-id-schedule",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/singlesend_schedule"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"type": "string"
},
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"500": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"type": "string"
},
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete Single Send Schedule",
"tags": [
"Single Sends"
],
"x-stoplight": {
"id": "DELETE_marketing-singlesends-id-schedule",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"put": {
"description": "**This endpoint allows you to schedule a Single Send for future delivery using a Single Send ID.**\n\nTo schedule a Single Send, you must pass a date string in ISO 8601 time format (yyyy-MM-ddTHH:mm:ssZ) using the required `send_at` field. For example, the ISO 8601 format for 9:00 AM UTC on May 6, 2020 would be `2020-05-06T09:00:00Z`. You may also pass the string `\"now\"` to send the Single Send immediately.",
"operationId": "PUT_marketing-singlesends-id-schedule",
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"send_at": "3752-01-28T23:21:52.575Z"
},
"properties": {
"send_at": {
"description": "This is the ISO 8601 time at which to send the Single Send; must be in future, or the string \"now\"",
"format": "date-time",
"type": "string"
}
},
"required": [
"send_at"
],
"type": "object"
}
}
}
},
"responses": {
"201": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"send_at": "3752-01-28T23:21:52.575Z",
"status": "scheduled"
}
}
},
"schema": {
"properties": {
"send_at": {
"description": "",
"format": "date-time",
"type": "string"
},
"status": {
"enum": [
"scheduled"
],
"type": "string"
}
},
"type": "object"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"type": "string"
},
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"500": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"type": "string"
},
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Schedule Single Send",
"tags": [
"Single Sends"
],
"x-stoplight": {
"id": "PUT_marketing-singlesends-id-schedule",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/marketing/stats/automations": {
"get": {
"description": "**This endpoint allows you to retrieve stats for all your Automations.**\n\nBy default, all of your Automations will be returned, but you can specify a selection by passing in a comma-separated list of Automation IDs as the value of the query string parameter `automation_ids`.\n\nResponses are paginated. You can limit the number of responses returned per batch using the `page_size` query string parameter. The default is 50, but you specify a value between 1 and 100.\n\nYou can retrieve a specific page of responses with the `page_token` query string parameter.",
"operationId": "getall-automation-stats",
"parameters": [
{
"description": "This endpoint returns all automation IDs if no `automation_ids` are specified.",
"explode": false,
"in": "query",
"name": "automation_ids",
"schema": {
"items": {
"type": "string"
},
"maxItems": 25,
"minItems": 1,
"type": "array"
},
"style": "form"
},
{
"$ref": "#/components/parameters/trait_pagination_page_size"
},
{
"$ref": "#/components/parameters/trait_pagination_page_token"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/automations-response"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Get All Automation Stats",
"tags": [
"Marketing Campaigns Stats"
],
"x-stoplight": {
"id": "getall-automation-stats",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/marketing/stats/automations/export": {
"get": {
"description": "**This endpoint allows you to export Automation stats as CSV data**.\n\nYou can specify one Automation or many: include as many Automation IDs as you need, separating them with commas, as the value of the `ids` query string paramter.\n\nThe data is returned as plain text response but in CSV format, so your application making the call can present the information in whatever way is most appropriate, or just save the data as a `.csv` file.",
"operationId": "get-automations-stats-export",
"parameters": [
{
"description": "The IDs of Automations for which to export stats.",
"explode": false,
"in": "query",
"name": "ids",
"schema": {
"items": {
"type": "string"
},
"maxItems": 50,
"minItems": 1,
"type": "array"
},
"style": "form"
},
{
"description": "The [IANA Area/Region](https://en.wikipedia.org/wiki/Tz_database#Names_of_time_zones) string representing the timezone in which the stats are to be presented; i.e. `\"America/Chicago\"`. This parameter changes the timezone format only; it does not alter which stats are returned.",
"in": "query",
"name": "timezone",
"schema": {
"default": "UTC",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"description": "CSV data",
"type": "string"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Export Automation Stats",
"tags": [
"Marketing Campaigns Stats"
],
"x-stoplight": {
"id": "get-automations-stats-export",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/marketing/stats/automations/{id}": {
"get": {
"description": "**This endpoint allows you to retrieve stats for a single Automation using its ID.**\n\nMultiple Automation IDs can be retrieved using the \"Get All Automation Stats\" endpoint. Once you have an ID, this endpoint will return detailed stats for the single automation specified.\n\nYou may constrain the stats returned using the `start_date` and `end_date` query string parameters. You can also use the `group_by` and `aggregated_by` query string parameters to further refine the stats returned.",
"operationId": "get-automation-stat",
"parameters": [
{
"$ref": "#/components/parameters/trait_automationQueryParams_group_by"
},
{
"$ref": "#/components/parameters/trait_automationQueryParams_step_ids"
},
{
"$ref": "#/components/parameters/trait_baseParams_aggregated_by"
},
{
"$ref": "#/components/parameters/trait_baseParams_start_date"
},
{
"$ref": "#/components/parameters/trait_baseParams_end_date"
},
{
"$ref": "#/components/parameters/trait_baseParams_timezone"
},
{
"$ref": "#/components/parameters/trait_pagination_page_size"
},
{
"$ref": "#/components/parameters/trait_pagination_page_token"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/automations-response"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_errorResponse_400"
},
"404": {
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Get Automation Stats by ID",
"tags": [
"Marketing Campaigns Stats"
],
"x-stoplight": {
"id": "get-automation-stat",
"mock": {
"dynamic": false,
"enabled": true,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
]
},
"/marketing/stats/automations/{id}/links": {
"get": {
"description": "**This endpoint lets you retrieve click-tracking stats for a single Automation**.\n\nThe stats returned list the URLs embedded in your Automation and the number of clicks each one received.\n\nResponses are paginated. You can limit the number of responses returned per batch using the `page_size` query string parameter. The default is 50, but you specify a value between 1 and 100.\n\nYou can retrieve a specific page of responses with the `page_token` query string parameter.",
"operationId": "get-automation-link-stat",
"parameters": [
{
"$ref": "#/components/parameters/trait_automationQueryParams_group_by"
},
{
"$ref": "#/components/parameters/trait_automationQueryParams_step_ids"
},
{
"$ref": "#/components/parameters/trait_pagination_page_size"
},
{
"$ref": "#/components/parameters/trait_pagination_page_token"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/automations-link-stats-response"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_errorResponse_400"
},
"404": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Get Automation Click Tracking Stats by ID",
"tags": [
"Marketing Campaigns Stats"
],
"x-stoplight": {
"id": "get-automation-link-stat",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"description": "The ID of the Automation you want to get click tracking stats for. ",
"in": "path",
"name": "id",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
}
]
},
"/marketing/stats/singlesends": {
"get": {
"description": "**This endpoint allows you to retrieve stats for all your Single Sends.**\n\nBy default, all of your Single Sends will be returned, but you can specify a selection by passing in a comma-separated list of Single Send IDs as the value of the query string parameter `singlesend_ids`.\n\nResponses are paginated. You can limit the number of responses returned per batch using the `page_size` query string parameter. The default is 50, but you specify a value between 1 and 100.\n\nYou can retrieve a specific page of responses with the `page_token` query string parameter.",
"operationId": "getall-singlesend-stats",
"parameters": [
{
"description": "This endpoint returns all Single Send IDs if no IDs are included in `singlesend_ids`.",
"explode": false,
"in": "query",
"name": "singlesend_ids",
"schema": {
"items": {
"type": "string"
},
"maxItems": 25,
"minItems": 1,
"type": "array"
},
"style": "form"
},
{
"$ref": "#/components/parameters/trait_pagination_page_size"
},
{
"$ref": "#/components/parameters/trait_pagination_page_token"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/singlesends-response"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_errorResponse_400"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Get All Single Sends Stats",
"tags": [
"Marketing Campaigns Stats"
],
"x-stoplight": {
"id": "getall-singlesend-stats",
"mock": {
"dynamic": false,
"enabled": true,
"statusCode": 200
},
"public": true
}
}
},
"/marketing/stats/singlesends/export": {
"get": {
"description": "**This endpoint allows you to export Single Send stats as .CSV data**.\n\nYou can specify one Single Send or many: include as many Single Send IDs as you need, separating them with commas, as the value of the `ids` query string paramter.\n\nThe data is returned as plain text response but in .CSV format, so your application making the call can present the information in whatever way is most appropriate, or just save the data as a .csv file.",
"operationId": "get-singlesend-stats-export",
"parameters": [
{
"description": "The IDs of Single Sends for which to export stats.",
"explode": false,
"in": "query",
"name": "ids",
"schema": {
"items": {
"type": "string"
},
"maxItems": 50,
"minItems": 1,
"type": "array"
},
"style": "form"
},
{
"description": "The [IANA Area/Region](https://en.wikipedia.org/wiki/Tz_database#Names_of_time_zones) string representing the timezone in which the stats are to be presented; i.e. `\"America/Chicago\"`. This parameter changes the timezone format only; it does not alter which stats are returned.",
"in": "query",
"name": "timezone",
"schema": {
"default": "UTC",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"description": "CSV data",
"type": "string"
}
}
},
"description": ""
},
"400": {
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Export Single Send Stats",
"tags": [
"Marketing Campaigns Stats"
],
"x-stoplight": {
"id": "get-singlesend-stats-export",
"mock": {
"dynamic": false,
"enabled": true,
"statusCode": 200
},
"public": true
}
}
},
"/marketing/stats/singlesends/{id}": {
"get": {
"description": "**This endpoint allows you to retrieve stats for an individual Single Send using a Single Send ID.**\n\nMultiple Single Send IDs can be retrieved using the \"Get All Single Sends Stats\" endpoint. Once you have an ID, this endpoint will return detailed stats for the Single Send specified.\n\nYou may constrain the stats returned using the `start_date` and `end_date` query string parameters. You can also use the `group_by` and `aggregated_by` query string parameters to further refine the stats returned.",
"operationId": "get-singlesend-stat",
"parameters": [
{
"$ref": "#/components/parameters/trait_baseParams_aggregated_by"
},
{
"$ref": "#/components/parameters/trait_baseParams_start_date"
},
{
"$ref": "#/components/parameters/trait_baseParams_end_date"
},
{
"$ref": "#/components/parameters/trait_baseParams_timezone"
},
{
"$ref": "#/components/parameters/trait_pagination_page_size"
},
{
"$ref": "#/components/parameters/trait_pagination_page_token"
},
{
"$ref": "#/components/parameters/trait_singlesendQueryParams_group_by"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/singlesends-response"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_errorResponse_400"
},
"404": {
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Get Single Send Stats by ID",
"tags": [
"Marketing Campaigns Stats"
],
"x-stoplight": {
"id": "get-singlesend-stat",
"mock": {
"dynamic": false,
"enabled": true,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
]
},
"/marketing/stats/singlesends/{id}/links": {
"get": {
"description": "**This endpoint lets you retrieve click-tracking stats for one Single Send**.\n\nThe stats returned list the URLs embedded in the specified Single Send and the number of clicks each one received.\n\nResponses are paginated. You can limit the number of responses returned per batch using the `page_size` query string parameter. The default is 50, but you specify a value between 1 and 100.\n\nYou can retrieve a specific page of responses with the `page_token` query string parameter.",
"operationId": "get-singlesend-link-stat",
"parameters": [
{
"$ref": "#/components/parameters/trait_pagination_page_size"
},
{
"$ref": "#/components/parameters/trait_pagination_page_token"
},
{
"$ref": "#/components/parameters/trait_singlesendQueryParams2_group_by"
},
{
"$ref": "#/components/parameters/trait_singlesendQueryParams2_ab_variation_id"
},
{
"$ref": "#/components/parameters/trait_singlesendQueryParams2_ab_phase_id"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/singlesends-link-stats-response"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_errorResponse_400"
},
"404": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Get Single Send Click Tracking Stats by ID",
"tags": [
"Marketing Campaigns Stats"
],
"x-stoplight": {
"id": "get-singlesend-link-stat",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
]
},
"/marketing/test/send_email": {
"post": {
"description": "**This endpoint allows you to send a test marketing email to a list of email addresses**.\n\nBefore sending a marketing message, you can test it using this endpoint. You may specify up to **10 contacts** in the `emails` request body field. You must also specify a `template_id` and include either a `from_address` or `sender_id`. You can manage your templates with the [Twilio SendGrid App](https://mc.sendgrid.com/dynamic-templates) or the [Transactional Templates API](https://sendgrid.api-docs.io/v3.0/transactional-templates).\n\n> Please note that this endpoint works with Dynamic Transactional Templates only. Legacy Transactional Templates will not be delivered.\n\nFor more information about managing Dynamic Transactional Templates, see [How to Send Email with Dynamic Transactional Templates](https://sendgrid.com/docs/ui/sending-email/how-to-send-an-email-with-dynamic-transactional-templates/).\n\nYou can also test your Single Sends in the [Twilio SendGrid Marketing Campaigns UI](https://mc.sendgrid.com/single-sends).",
"operationId": "POST_marketing-test-send_email",
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"custom_unsubscribe_url": "https://example.com/unsubscribe",
"emails": [
"janedoe@example.com",
"tiramisu@example.com",
"bundt@example.com"
],
"sender_id": 6060664,
"suppression_group_id": 21865513,
"template_id": "f8f77db8-b9fa-4b3c-9ee8-de3d582016b8",
"version_id_override": "7734f757-8eb8-4d22-b7f0-779a72f32986"
},
"properties": {
"custom_unsubscribe_url": {
"description": "A custom unsubscribe URL.",
"type": "string"
},
"emails": {
"description": "An array of email addresses you want to send the test message to.",
"items": {
"format": "email",
"type": "string"
},
"maxItems": 10,
"minItems": 1,
"type": "array",
"uniqueItems": true
},
"from_address": {
"description": "You can either specify this address or specify a verified sender ID.",
"format": "email",
"type": "string"
},
"sender_id": {
"description": "This ID must belong to a verified sender. Alternatively, you may supply a `from_address` email.",
"type": "integer"
},
"suppression_group_id": {
"type": "integer"
},
"template_id": {
"description": "The ID of the template that you would like to use. If you use a template that contains a subject and content (either text or HTML), then those values specified at the personalizations or message level will not be used.",
"format": "uuid",
"type": "string"
},
"version_id_override": {
"description": " You can override the active template with an alternative template version by passing the version ID in this field. If this field is blank, the active template version will be used.",
"format": "uuid",
"type": "string"
}
},
"required": [
"template_id",
"emails"
],
"type": "object"
}
}
}
},
"responses": {
"202": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_errorResponse_400"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Send a Test Marketing Email",
"tags": [
"Send Test Email"
],
"x-stoplight": {
"id": "POST_marketing-test-sendemail",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/messages": {
"get": {
"description": "This is **BETA** functionality. You may not have access, and we reserve the right to change functionality without notice.\n\nFilter all messages to search your Email Activity. All queries need to be [URL encoded](https://meyerweb.com/eric/tools/dencoder/), and have this format:\n\n`query={query_type}=\"{query_content}\"`\n\nencoded, this would look like this:\n\n`query=type%3D%22query_content%22`\n\nfor example:\n\nFilter by a specific email - `query=to_email%3D%22example%40example.com%22`\n\nFilter by subject line - `query=subject%3d%22A%20Great%20Subject%22`\n\n**Full list of basic query types and examples:**\n\n\n| **Filter query** | **Unencoded Example** (put this one into the try it out query - it'll automatically encode it for you) | **Encoded Example** (use this one in your code) |\n|-----------------|----------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------|\n| msg_id | msg_id=“filter0307p1las1-16816-5A023E36-1.0” | msg_id%3D%22filter0307p1las1-16816-5A023E36-1.0%22 |\n| from_email | from_email=“testing@sendgrid.net” | from_email%3D%22testing%40sendgrid.net%22 |\n| subject | subject=\"This is a subject test\" | subject%22This%20is%20a%20subject%20test%22 |\n| to_email | to_email=\"example@example.com\" | to_email%3D%22example%40example.com%22 |\n| status | | status%22processed%22 |\n| template_id | | |\n| asm_group_id | | |\n| api_key_id | | |\n| events | status=\"processed\" | status%3D%22processed%22 |\n| originating_ip | | |\n| categories | | |\n| unique_args | | |\n| outbound_ip | | |\n| last_event_time | last_event_time=“2017-11-07T23:13:58Z” | last_event_time%3D%E2%80%9C2017-11-07T23%3A13%3A58Z%E2%80%9D |\n| clicks | clicks=\"0\" | clicks%3D%220%22 |\n\nFor information about building compound queries, and for the full query language functionality, see the [query language reference](https://docs.google.com/a/sendgrid.com/document/d/1fWoKTFNfg5UUsB6t9KuIcSo9CetKF_T0bGfWJ_gdPCs/edit?usp=sharing).\n\nComing soon, example compound queries: limit + to email + date",
"operationId": "GET-messages",
"parameters": [
{
"description": "Use the query syntax to filter your email activity.",
"in": "query",
"name": "query",
"required": true,
"schema": {
"type": "string"
}
},
{
"description": "The number of messages returned. This parameter must be greater than 0 and less than or equal to 1000",
"in": "query",
"name": "limit",
"required": false,
"schema": {
"default": 10,
"maximum": 1000,
"minimum": 1,
"type": "number"
}
},
{
"in": "header",
"name": "X-Query-Id",
"required": false,
"schema": {
"type": "string"
}
},
{
"in": "header",
"name": "X-Cursor",
"required": false,
"schema": {
"type": "string"
}
},
{
"$ref": "#/components/parameters/trait_authorizationHeader_Authorization"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"messages": [
{
"clicks_count": 0,
"from_email": "from@test.com",
"last_event_time": "2017-10-13T18:56:21Z",
"last_timestamp": 1495064728,
"msg_id": "abc123",
"opens_count": 0,
"status": "processed",
"subject": "something profound",
"to_email": "to@test.com"
},
{
"clicks_count": 200,
"from_email": "yeah@test.com",
"last_event_time": "2017-10-13T18:56:21Z",
"last_timestamp": 1495064793,
"msg_id": "321befe",
"opens_count": 500,
"status": "delivered",
"subject": "something profound",
"to_email": "nah@test.com"
},
{
"clicks_count": 0,
"from_email": "sad@test.com",
"last_event_time": "2017-10-13T18:56:21Z",
"last_timestamp": 1495064993,
"msg_id": "434512dfg",
"opens_count": 0,
"status": "not_delivered",
"subject": "something sad",
"to_email": "reject@test.com"
}
]
}
}
},
"schema": {
"properties": {
"messages": {
"items": {
"allOf": [
{
"$ref": "#/components/schemas/email-activity-response-common-fields"
},
{
"properties": {
"clicks_count": {
"description": "The number of times links in the message were clicked.",
"type": "integer"
},
"last_event_time": {
"description": "A timestamp of the last event received for the specific message in iso 8601 format.",
"type": "string"
},
"opens_count": {
"description": "The number of times the message was opened.",
"type": "integer"
}
},
"type": "object"
}
],
"example": {
"clicks_count": 2,
"from_email": "from@test.com",
"last_event_time": "2017-10-13T18:56:21Z",
"msg_id": "abc123",
"opens_count": 1,
"status": "processed",
"subject": "anim Duis sint veniam",
"to_email": "test@test.com"
},
"properties": {
"clicks_count": {
"type": "integer"
},
"from_email": {
"type": "string"
},
"last_event_time": {
"description": "iso 8601 format",
"type": "string"
},
"msg_id": {
"type": "string"
},
"opens_count": {
"type": "integer"
},
"status": {
"enum": [
"processed",
"delivered",
"not_delivered"
],
"type": "string"
},
"subject": {
"type": "string"
},
"to_email": {
"type": "string"
}
},
"required": [
"from_email",
"msg_id",
"subject",
"to_email",
"status",
"opens_count",
"clicks_count",
"last_event_time"
],
"title": "Abbv. Message",
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"message": "invalid syntax: 'bad_field' is not a known field"
}
]
}
}
},
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"message": {
"type": "string"
}
},
"required": [
"message"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"errors"
],
"type": "object"
}
}
},
"description": ""
},
"429": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"message": "too many requests"
}
]
}
}
},
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Filter all messages",
"tags": [
"Query",
"Messages"
],
"x-stoplight": {
"id": "GET-messages",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/messages/download": {
"post": {
"description": "This is BETA functionality. You may not have access, and we reserve the right to change functionality without notice.\n\nThis request will kick off a backend process to generate a CSV file. Once generated, the worker will then send an email for the user download the file. The link will expire in 3 days.\n\nThe CSV fill contain the last 1 million messages. This endpoint will be rate limited to 1 request every 12 hours (rate limit may change).\n\nThis endpoint is similar to the GET Single Message endpoint - the only difference is that /download is added to indicate that this is a CSV download requests but the same query is used to determine what the CSV should contain.",
"operationId": "POST_v3-messages-download",
"parameters": [
{
"description": "Uses a SQL like syntax to indicate which messages to include in the CSV",
"in": "query",
"name": "query",
"required": false,
"schema": {
"type": "string"
}
},
{
"$ref": "#/components/parameters/trait_authorizationHeader_Authorization"
}
],
"responses": {
"202": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"message": "An email will be sent to jane_doe@example.com when the CSV is ready to download.",
"status": "pending"
}
}
},
"schema": {
"properties": {
"message": {
"type": "string"
},
"status": {
"enum": [
"pending"
],
"type": "string"
}
},
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"message": "some error"
}
]
}
}
},
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"429": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": "",
"message": "too many requests"
}
]
}
}
},
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"500": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"message": "internal server error"
}
]
}
}
},
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Request CSV",
"tags": [
"CSV (UI only)",
"V3"
],
"x-stoplight": {
"id": "POST_v3-messages-download",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/messages/download/{download_uuid}": {
"get": {
"description": "**This endpoint will return a presigned URL that can be used to download the CSV that was requested from the \"Request a CSV\" endpoint.**",
"operationId": "GET_v3-messages-download-download_uuid",
"parameters": [
{
"$ref": "#/components/parameters/trait_authorizationHeader_Authorization"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"csv": "https://s3-us-west-2.amazonaws.com/sendgrid-rts-development-queries/013c31af-7c9a-49e6-922c-d990f4aff151.csv?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=3600&X-Amz-Credential=AKIAI4NOW7APZHRFYGWQ%2F20170728%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20170728T223936Z&X-Amz-Signature=5c13ede58b211799ab1a556280bd316c404eac3aef1450c47906a077166c4ab4",
"presigned_url": "https://example.com"
}
}
},
"schema": {
"properties": {
"csv": {
"description": "Returns the aws signed link to the csv file which mako UI should perform a get on to trigger the csv download for the user",
"minLength": 5,
"pattern": "^((http[s]?|ftp):\\/)?\\/?([^:\\/\\s]+)((\\/\\w+)*\\/)([\\w\\-\\.]+[^#?\\s]+)(.*)?(#[\\w\\-]+)?$",
"type": "string"
},
"presigned_url": {
"description": "A signed link that will allow you to download the CSV file requested by the Request a CSV endpoint.",
"format": "uri",
"type": "string"
}
},
"required": [
"csv"
],
"type": "object"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": "",
"message": "download token is invalid or expired"
}
]
}
}
},
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"500": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"message": "internal server error"
}
]
}
}
},
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Download CSV",
"tags": [
"CSV (UI only)",
"V3"
],
"x-stoplight": {
"id": "GET_v3-messages-download-download_uuid",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"description": "UUID used to locate the download csv request entry in the DB.\n\nThis is the UUID provided in the email sent to the user when their csv file is ready to download",
"in": "path",
"name": "download_uuid",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
}
]
},
"/messages/{msg_id}": {
"get": {
"description": "This is BETA functionality. You may not have access, and we reserve the right to change functionality without notice.\n\nGet all of the details about the specified message.",
"operationId": "GET-v3-messages-msg_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_authorizationHeader_Authorization"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"api_key_id": "bUshz3LGa3coToxA2sWViYAEJJmYZJRyY9BCQY",
"asm_group_id": 5464443,
"categories": [
"dolor",
"pie"
],
"events": [
{
"attempt_num": 8,
"bounce_type": "soft",
"event_name": "unsubscribe",
"http_user_agent": "Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko Firefox/11.0",
"mx_server": "s",
"processed": "2017-10-13T18:56:21Z",
"url": "G_3c-1HtUkN4`puC&&!u>0OiStFG.ZoRi2=j%fUVa]&re6k{hKqD-8vWE12Fb5JC6z>S@@'cWS~w5KtuIwv$8/JDD94CXx1n5yjC_I2lQ66zDj4MXe/4bqlcqqQ7evnQWTYx5roaEYMapyuzb/USpsyalh/HcYc9PmQfF8ND_C7bXnwFQ_fb_BHMXbIV8JN28/NjZdawDJ6kSWxLykSVTHzcISGPBRfs_rt3Uc65Vzj6LDSSMN8WRs70m0tAWs/fkDvjvm_7ZeV08YZeB9j9mS9BcE089Fn5UzDlTJW9vqDF5uipjlIVrNbM7oWE/MIJcjg2vJO20jA24WHrchrEXCvKcxriviSDl3tuyDxqdqRSekpm2aH6yW7_ylXj/nWsex4jm3rKvYw1uLq3Tp7qb9edhj8B_TnwLv1yjHSkgbA5jKI4BTqxugmwVTnFf2OFCFp/ZILLkoKgfwOyIK4reUIkhCjuhkwp/cqGaFAkeCFQXPB6DLYesLZ2M3KPuBsBrs3pr3HRbTtwaOzdKtGc8e0C0VTjrJ3GwljStMPrSuQWh6/vigHRasZ4P9kFv5DPVbHWZzPvtwUw0AMByt44YH678WpbAXXy4I/IVOHmErZTbw1mJ/3vd4uI5rr2zEO_YA9qJZLJT/wmBimbOyaMtmTNYr_FhgfkKMN3_1la7RCK8CIP3p26mbuHbdJV1j/5sTIKibInM5l2BoWdEi49bPqzagsfjKpGVbg0YQ8mjrLhI92/qy0eVYi34kBGVuLzxK2FLC8vwYUrbupjUYE23Mc_6nmHYRK1HF1QmZDZG1hw96I3MPbTZqeJOWGch230qDNxOgnHRNNM52k/3c7FeuRr88RwZGpif/4FaSAbdqkUNvJ9J9qX2tJS9x5vZlgD8k4YHIXDztrnwg2VPquj/uo_2MjbWybIF/NGJM2RFAsKV1S5iOejuTV4p12KlH1p0Dt5EpxCSIl0XoWuvyLYar77f_hzqNdWAyL0FDxGfj4ma4jwqdTTLNyeZEtguYoCHTFfY/HgJkpHx/yO23G7gLhKPvD459ceffHidFh7LipTxNF0GFXhIAPrWfhv7PkPmVofBoFFlo6/rMcHQ82d5VS8i1CCyLtfuT5WH9GqrsOY7xo3lxi7BNL93/PLRdQT3SObRFRERw68V5ZFvIuEQqFOFZQ848rWPLXYDGY658dyjZALf/Ug4EROi_ehNtzPwecer_RGBHxeMnpxrPAFZEL6YXNKzm8hh3HY4Uc4fgkjge5fXsR4CeTSkS66/FOD00deDKmN7XcHEj1LGlAmd4XlV8vFpXg2VazX4OLW8z6vXn5vntNGYO6eBCEKUwupRz9nQSMeZ/Pbmjoopyt6TxQBUfPkHBdgCIhqA1zDV72ARqGlK_ao9KVjvgbB98YeiieIyogkuOa4y0E5iUEdBopzovVgtY88yLijh9ww/tl0R5rI2P2_OxguTbv_wrfEm8jRjISEIqSE9q29RB3n8PeD4hu24rcsaEuTMCqniiLN0a2OtZiKxHnbsNB660Aq3/tEo2SHZBkkIFYXBbDNE7gLfvFz9ZaC_E2oV2quyK1Id5SkNkJBVRRgROWBc_XEOXktc4vRUKxy1MQ526Xilyo8/uI67lHH1Vlr4V3GVmweT4A16KMqzmVzvRDRFLpkBv2iu3Okc1vIqkC/426aOqeUm6SXIx16d8BWVRcmqKqizxDEF3JkLFgX0ab73CZ4GdJ9YaakJO7y4adFGzzIVLcn08UZ/pwDQ9BAuwuMc2yMnKihdvmLKbnAa1ATk9jFXQ9QAEMBHZLbPNvtS3pkpk0s6fyh2ceHa9Myy3fL/oqvPmq_14KzLgPZHaOlyb3tUoQM52fv/I78TqTGyB4WYD_vkm8gYHZcCF0dFIXsiXUbAbwR90Ldk8lsgxSL6rBvjPSlQq7N66NPzUVRYr3zSISupG_66uS4rJszHwmxmmraT3/zfVKxHXFHgxUDRmnwIMfdFKm4sf/qnRRccOLExJYGcZy8u65jo4gHDvO6vnpsdf0YtVWqDBJXa95/Y/qL7EYS73_t6/2xnWra9TTO0OtQNXEQ1XXLSLt6vPw1FTlE2aYCitDgUo7DyxiwuGtvmMKUkYCo/lqXouo2TGXZFF80lrCu1vKdxBgOOerlrsKrOJawGEzL8XzXdxkOMUT8HOzHPNOvDwsxc47mvpVzXEbX5DRaioOlm2U4flTG/6bZfLqJqHNtrKwC5U5NNG6_yNOW5Jg_bLmzUosi86IXxn7i04vRXXn92JmdE70TcdujehT1n15wtiD8ld5A65IV2W1801/wr0XcDIGiUSmxFfCozUCtBTFwln0uJ0cquSDXhj7JQADhYDGyz8WhqcPE7CP9xN93EUBrWczINbA4IsfckY8CZh68/Tak5dEhw8i_3rz/8U39D/iML1G_A8TsnKQ9/_S8o89fFc7Wx/f1nXF8H6OLVbPpp5IZg7UTZ2K0bSe3iBpsmkkJpxY_6zEHTJE3LbIANwF9Ik5Tu0ZJpjck7I07xlHR8mDW9FXclSQC/qJUGL5qByf2SY2Wd24hqKGrahLfqApQuRI8_DtU/Y8kH6DDif5kD6as3sIe6VbKYnU9c1URq/npTlE72m107FXxW9zktdzbBJAxt4udMFzjt2LPcBpqrMKGkrg4BHqKXwhFTspCWyYCjxJ/eFb3fd3BB5kb9D0yl3oOjeWtbJBqsboyzdLisRBn2MCtXO2O8eo4Hkm/2uJDoRjRCyIi2GNRkH0B3EkN6Z3vG40C985bAtM8eqHABzP2QRcKCz4ICOw_Xz249bJk8qM0/m4EeIWnx2ISf9TBU6_0KZ5QJ0VPOCPXxV9jCeK5W5/RV5nd7GUUXG0btDqUAa3DzpaRHX3klMAqL73hK4AGD3ItikmxF8vnSaqtsgOCpEePERt9qcUOJJP2aR0scPAf_1TkGSrgr5VF/XLM2i7YhC_3J9fA2Qwa0dbTedY/xGayjimEkuWSWvh7P5FNOum_l7qJPnA31lQq6ixqR0NKhO328rWfijqKHF6WR/5O0MJ4WNuIXk6xBtTOA9mK7CGUgWjaF5mB72PVnDpN8G6ERO39GqO/fCO96/A1mwIPWedF6HklU8jaQ_M5EUzwCsBE3/7FW2hbcD3GCOFCiLvObjn59o6CKoYlmTop/PZw2CLzvARAr5KaLhjIuZRSKTGlmYjvSoSELuvWi/QHUV7SJ0kF_1O3b_3a88cm/z7qD3Rp6MmoQdGPSyu1lXTpoETypgOMywfsr4ycV2LQr5XaE3UrQP/RzobA4rI_I3ceCUaRASmil2rV1TUiyljhdCFt8zmi1o2NyTzSBRNGP0lXU5Qtm6dKKp7lGRC19P2oSSFrxt85vWRNo1mv8odcL/TF7/MN1Ev7gY20MqlRSBrlwg5LZ4_Az7QnBnpbU3LkTC/oVb743VFtXMW4cK/3lw6wfBJZe_8DobxT_6/gCXp6QOs/LuqmrQHMQvTS6jfbqVFnPfLjrQ01Mb_F4lr3md6m87wpc1CYd9hdzgUL/aqz68HMDCxjGauC00Rajq9wGVYcWJ3j6rIaHIPwclftjARXFDg0yH8v/L6qDgIgGwbB3eZfjOXHAMXRFNMMihseZxYkcFAzLtYr94q9XpQeK4bKi9h_rWAMwcEHnS8/MHHlySbgy8azGEA0u9xsY96MRi45Qe74kZ9xlsI1t4Yutx8/gsjIiu7vNsKEyeTwd88BMExjWNcJHOSHRff57pJBMEAtPdbMETYorvUkRyErsqprxX0W45n0RRQ85w/JCOsYaxZOAFfzO0AdLpMCguFwl01fkFOKYrQeXfNn8w0KF4XS0k/fPx02fU7fFjUoXcPH7_9xo755WL8DNIU2ne4b6DpiROe473yUfD8_zSNUI1tpxzVYNA7GvVSYt8UtqHn3/_QwuOc4VeAI6RiAG5O5bcAxzQl96Q35emNwtTT_CYqHORCmyPj6By2hT_SCLf8m_xFxxe3YzvnxDZGq3qf__pq7Tw181/GosBAJy3MotiIxcYDASbY9sV4T2V/KoGHyJ64spdkQbJOHJ216JMSKj8ii5m8gxqJ2ypL81p5hbjWtjbUgSc8M_KTLuc0Owf1R/3vr/dKbQ2pJ4XEfXhnZTBQY7ZrClnVCnd5cMe9ic/aNF0yyOSQVVOecUFkK9IYFVR8VHdVf4/Nfu3nOEskHki1_r3At1HLOMniS6qNTvhS0hfqIuBiQBsd5aB7OdfVpYy1HdIR72gMToBlpHPsE5GrVO0J9/gcyB2xcyZ3UpTom8G3V48LUkk6kcJ6l1SL5Fgzbst0z3pDA4dzBfswbC8dW57_MkswDANNd8atPrOBSU5m2z66dP/mIYQ8iq/DcmBexkARDI47VzYuw5gwy8Lvym2_B2gxBokU9_T/fHCPjlpqjTsY6SgBOz1nlDh_HcSWYAqnyrZxbEO2erVJ4WPKNzjM3KaPXGH/dZryna1E28wxCrMqLCs9aL9oVBlDMjUcEryAyRh7xwN0uWGopdpkd7Du6O9EPjAj37sHkUiVs7WL6JyexoDF_n67MICvQnJ9FK/FVrp1uMZnmr7ijkMW87moNRBkXbVc2EA_hHOHmpbVGqr6WgNtJ7bBk1LrAPT8sKtE75vbe_L9VYqBHJ/njk.WIIj-V23pwC7ZahcIL0XnDPupL7ltwEc779Ofhrk9dt_wIOFsA8XwnCjrYqH2ty.F0XdS\"*;@kDYgfL4dwE/5I@>k|u0D:wGz\"_8=}RJM!Ybbwd}eN=ZB*esF&(iQ%FW]_FSA:3Ze4O*6&tG-Fe**/j^a&S8zIa#6gxL2NmnNMSVGF-Bf3z08tt0ug_UfNshhs4HJh0l1o24gjAN-Uck1OvWkGQSXH0glB7CnOm0gI"
},
{
"attempt_num": 3,
"bounce_type": "hard",
"event_name": "dropped",
"http_user_agent": "quis re",
"mx_server": "laborum nisi",
"processed": "2017-10-13T18:56:21Z",
"url": "G_3c-1HtUkN4`puC&&!u>0OiStFG.ZoRi2=j%fUVa]&re6k{hKqD-8vWE12Fb5JC6z>S@@'cWS~w5KtuIwv$8/JDD94CXx1n5yjC_I2lQ66zDj4MXe/4bqlcqqQ7evnQWTYx5roaEYMapyuzb/USpsyalh/HcYc9PmQfF8ND_C7bXnwFQ_fb_BHMXbIV8JN28/NjZdawDJ6kSWxLykSVTHzcISGPBRfs_rt3Uc65Vzj6LDSSMN8WRs70m0tAWs/fkDvjvm_7ZeV08YZeB9j9mS9BcE089Fn5UzDlTJW9vqDF5uipjlIVrNbM7oWE/MIJcjg2vJO20jA24WHrchrEXCvKcxriviSDl3tuyDxqdqRSekpm2aH6yW7_ylXj/nWsex4jm3rKvYw1uLq3Tp7qb9edhj8B_TnwLv1yjHSkgbA5jKI4BTqxugmwVTnFf2OFCFp/ZILLkoKgfwOyIK4reUIkhCjuhkwp/cqGaFAkeCFQXPB6DLYesLZ2M3KPuBsBrs3pr3HRbTtwaOzdKtGc8e0C0VTjrJ3GwljStMPrSuQWh6/vigHRasZ4P9kFv5DPVbHWZzPvtwUw0AMByt44YH678WpbAXXy4I/IVOHmErZTbw1mJ/3vd4uI5rr2zEO_YA9qJZLJT/wmBimbOyaMtmTNYr_FhgfkKMN3_1la7RCK8CIP3p26mbuHbdJV1j/5sTIKibInM5l2BoWdEi49bPqzagsfjKpGVbg0YQ8mjrLhI92/qy0eVYi34kBGVuLzxK2FLC8vwYUrbupjUYE23Mc_6nmHYRK1HF1QmZDZG1hw96I3MPbTZqeJOWGch230qDNxOgnHRNNM52k/3c7FeuRr88RwZGpif/4FaSAbdqkUNvJ9J9qX2tJS9x5vZlgD8k4YHIXDztrnwg2VPquj/uo_2MjbWybIF/NGJM2RFAsKV1S5iOejuTV4p12KlH1p0Dt5EpxCSIl0XoWuvyLYar77f_hzqNdWAyL0FDxGfj4ma4jwqdTTLNyeZEtguYoCHTFfY/HgJkpHx/yO23G7gLhKPvD459ceffHidFh7LipTxNF0GFXhIAPrWfhv7PkPmVofBoFFlo6/rMcHQ82d5VS8i1CCyLtfuT5WH9GqrsOY7xo3lxi7BNL93/PLRdQT3SObRFRERw68V5ZFvIuEQqFOFZQ848rWPLXYDGY658dyjZALf/Ug4EROi_ehNtzPwecer_RGBHxeMnpxrPAFZEL6YXNKzm8hh3HY4Uc4fgkjge5fXsR4CeTSkS66/FOD00deDKmN7XcHEj1LGlAmd4XlV8vFpXg2VazX4OLW8z6vXn5vntNGYO6eBCEKUwupRz9nQSMeZ/Pbmjoopyt6TxQBUfPkHBdgCIhqA1zDV72ARqGlK_ao9KVjvgbB98YeiieIyogkuOa4y0E5iUEdBopzovVgtY88yLijh9ww/tl0R5rI2P2_OxguTbv_wrfEm8jRjISEIqSE9q29RB3n8PeD4hu24rcsaEuTMCqniiLN0a2OtZiKxHnbsNB660Aq3/tEo2SHZBkkIFYXBbDNE7gLfvFz9ZaC_E2oV2quyK1Id5SkNkJBVRRgROWBc_XEOXktc4vRUKxy1MQ526Xilyo8/uI67lHH1Vlr4V3GVmweT4A16KMqzmVzvRDRFLpkBv2iu3Okc1vIqkC/426aOqeUm6SXIx16d8BWVRcmqKqizxDEF3JkLFgX0ab73CZ4GdJ9YaakJO7y4adFGzzIVLcn08UZ/pwDQ9BAuwuMc2yMnKihdvmLKbnAa1ATk9jFXQ9QAEMBHZLbPNvtS3pkpk0s6fyh2ceHa9Myy3fL/oqvPmq_14KzLgPZHaOlyb3tUoQM52fv/I78TqTGyB4WYD_vkm8gYHZcCF0dFIXsiXUbAbwR90Ldk8lsgxSL6rBvjPSlQq7N66NPzUVRYr3zSISupG_66uS4rJszHwmxmmraT3/zfVKxHXFHgxUDRmnwIMfdFKm4sf/qnRRccOLExJYGcZy8u65jo4gHDvO6vnpsdf0YtVWqDBJXa95/Y/qL7EYS73_t6/2xnWra9TTO0OtQNXEQ1XXLSLt6vPw1FTlE2aYCitDgUo7DyxiwuGtvmMKUkYCo/lqXouo2TGXZFF80lrCu1vKdxBgOOerlrsKrOJawGEzL8XzXdxkOMUT8HOzHPNOvDwsxc47mvpVzXEbX5DRaioOlm2U4flTG/6bZfLqJqHNtrKwC5U5NNG6_yNOW5Jg_bLmzUosi86IXxn7i04vRXXn92JmdE70TcdujehT1n15wtiD8ld5A65IV2W1801/wr0XcDIGiUSmxFfCozUCtBTFwln0uJ0cquSDXhj7JQADhYDGyz8WhqcPE7CP9xN93EUBrWczINbA4IsfckY8CZh68/Tak5dEhw8i_3rz/8U39D/iML1G_A8TsnKQ9/_S8o89fFc7Wx/f1nXF8H6OLVbPpp5IZg7UTZ2K0bSe3iBpsmkkJpxY_6zEHTJE3LbIANwF9Ik5Tu0ZJpjck7I07xlHR8mDW9FXclSQC/qJUGL5qByf2SY2Wd24hqKGrahLfqApQuRI8_DtU/Y8kH6DDif5kD6as3sIe6VbKYnU9c1URq/npTlE72m107FXxW9zktdzbBJAxt4udMFzjt2LPcBpqrMKGkrg4BHqKXwhFTspCWyYCjxJ/eFb3fd3BB5kb9D0yl3oOjeWtbJBqsboyzdLisRBn2MCtXO2O8eo4Hkm/2uJDoRjRCyIi2GNRkH0B3EkN6Z3vG40C985bAtM8eqHABzP2QRcKCz4ICOw_Xz249bJk8qM0/m4EeIWnx2ISf9TBU6_0KZ5QJ0VPOCPXxV9jCeK5W5/RV5nd7GUUXG0btDqUAa3DzpaRHX3klMAqL73hK4AGD3ItikmxF8vnSaqtsgOCpEePERt9qcUOJJP2aR0scPAf_1TkGSrgr5VF/XLM2i7YhC_3J9fA2Qwa0dbTedY/xGayjimEkuWSWvh7P5FNOum_l7qJPnA31lQq6ixqR0NKhO328rWfijqKHF6WR/5O0MJ4WNuIXk6xBtTOA9mK7CGUgWjaF5mB72PVnDpN8G6ERO39GqO/fCO96/A1mwIPWedF6HklU8jaQ_M5EUzwCsBE3/7FW2hbcD3GCOFCiLvObjn59o6CKoYlmTop/PZw2CLzvARAr5KaLhjIuZRSKTGlmYjvSoSELuvWi/QHUV7SJ0kF_1O3b_3a88cm/z7qD3Rp6MmoQdGPSyu1lXTpoETypgOMywfsr4ycV2LQr5XaE3UrQP/RzobA4rI_I3ceCUaRASmil2rV1TUiyljhdCFt8zmi1o2NyTzSBRNGP0lXU5Qtm6dKKp7lGRC19P2oSSFrxt85vWRNo1mv8odcL/TF7/MN1Ev7gY20MqlRSBrlwg5LZ4_Az7QnBnpbU3LkTC/oVb743VFtXMW4cK/3lw6wfBJZe_8DobxT_6/gCXp6QOs/LuqmrQHMQvTS6jfbqVFnPfLjrQ01Mb_F4lr3md6m87wpc1CYd9hdzgUL/aqz68HMDCxjGauC00Rajq9wGVYcWJ3j6rIaHIPwclftjARXFDg0yH8v/L6qDgIgGwbB3eZfjOXHAMXRFNMMihseZxYkcFAzLtYr94q9XpQeK4bKi9h_rWAMwcEHnS8/MHHlySbgy8azGEA0u9xsY96MRi45Qe74kZ9xlsI1t4Yutx8/gsjIiu7vNsKEyeTwd88BMExjWNcJHOSHRff57pJBMEAtPdbMETYorvUkRyErsqprxX0W45n0RRQ85w/JCOsYaxZOAFfzO0AdLpMCguFwl01fkFOKYrQeXfNn8w0KF4XS0k/fPx02fU7fFjUoXcPH7_9xo755WL8DNIU2ne4b6DpiROe473yUfD8_zSNUI1tpxzVYNA7GvVSYt8UtqHn3/_QwuOc4VeAI6RiAG5O5bcAxzQl96Q35emNwtTT_CYqHORCmyPj6By2hT_SCLf8m_xFxxe3YzvnxDZGq3qf__pq7Tw181/GosBAJy3MotiIxcYDASbY9sV4T2V/KoGHyJ64spdkQbJOHJ216JMSKj8ii5m8gxqJ2ypL81p5hbjWtjbUgSc8M_KTLuc0Owf1R/3vr/dKbQ2pJ4XEfXhnZTBQY7ZrClnVCnd5cMe9ic/aNF0yyOSQVVOecUFkK9IYFVR8VHdVf4/Nfu3nOEskHki1_r3At1HLOMniS6qNTvhS0hfqIuBiQBsd5aB7OdfVpYy1HdIR72gMToBlpHPsE5GrVO0J9/gcyB2xcyZ3UpTom8G3V48LUkk6kcJ6l1SL5Fgzbst0z3pDA4dzBfswbC8dW57_MkswDANNd8atPrOBSU5m2z66dP/mIYQ8iq/DcmBexkARDI47VzYuw5gwy8Lvym2_B2gxBokU9_T/fHCPjlpqjTsY6SgBOz1nlDh_HcSWYAqnyrZxbEO2erVJ4WPKNzjM3KaPXGH/dZryna1E28wxCrMqLCs9aL9oVBlDMjUcEryAyRh7xwN0uWGopdpkd7Du6O9EPjAj37sHkUiVs7WL6JyexoDF_n67MICvQnJ9FK/FVrp1uMZnmr7ijkMW87moNRBkXbVc2EA_hHOHmpbVGqr6WgNtJ7bBk1LrAPT8sKtE75vbe_L9VYqBHJ/njk.WIIj-V23pwC7ZahcIL0XnDPupL7ltwEc779Ofhrk9dt_wIOFsA8XwnCjrYqH2ty.F0XdS\"*;@kDYgfL4dwE/5I@>k|u0D:wGz\"_8=}RJM!Ybbwd}eN=ZB*esF&(iQ%FW]_FSA:3Ze4O*6&tG-Fe**/j^a&S8zIa#6gxL2NmnNMSVGF-Bf3z08tt0ug_UfNshhs4HJh0l1o24gjAN-Uck1OvWkGQSXH0glB7CnOm0gI"
}
],
"from_email": "LKb.pOGYIZbfxMgi7Le0K1YWym%e5pz2PiuGbwByM2tR+wRPqfoFg1__35DaWF8CDhzYJg@ZTSFDjbgdxD23oq34Lun1NUnyymoGT4G.5i1IEzQpXWAvNwx.BNckAJiBCnwEVnwBfrbMlouoOPmtTSEbdPp",
"id": "2mMUdxV2HRfAeDiBTYs2IP",
"msg_id": "hznSfJ1RfKeIKS1B4mShNnkRMpdt5IXXKf",
"originating_ip": "204.173.18.0",
"outbound_ip": "181.40.184.87",
"outbound_ip_type": "dedicated",
"status": "delivered",
"subject": "culpa aute amet magna sint",
"teammate": "9a8F0EP88wSSJeuOyXtbfc7BkK",
"template_id": "d674d9b7-e8e5-4e30-87be-1b3b026235fd",
"to_email": "+lOnUtHodefMsuBw.kunNalfcxRos5USSRoR@r5uEXYPP6wrQGRQ-vBg33fLY6MVkkMd-DKT8Z1iqt4qnrL23xMOZ8GWd.fyghGEFceTHIUpgwNGlwEtFHJikSaPzsnYfrJPKydPVPAlItJtqykRHZSGW",
"unique_args": "eu"
}
}
},
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/email-activity-response-common-fields"
},
{
"example": {
"api_key_id": "sdfsdfsdf123",
"asm_group_id": 11376349,
"categories": [
"hi",
"bye"
],
"events": [
{
"bounce_type": "soft",
"event_name": "bounced",
"http_user_agent": "in tempor ex dolore est",
"mx_server": "quis proident",
"processed": "2017-10-13T18:56:21Z",
"server_response": "some error message"
}
],
"from_email": "jane_doe@example.com",
"msg_id": "in aliquip id aliqua",
"originating_ip": "2.3.4.5",
"outbound_ip": "1.2.3.4",
"outbound_ip_type": "dedicated",
"status": "not_delivered",
"subject": "est incididunt adipisicing pariatur",
"teammate": "",
"template_id": "123e4567-e89b-12d3-a456-426655440000",
"to_email": "send@test.com",
"unique_args": "{'key': 'value'}"
},
"properties": {
"api_key_id": {
"description": "The ID of the API Key used to authenticate the sending request for the message.",
"maxLength": 50,
"minLength": 3,
"pattern": "^[A-Za-z0-9]+",
"type": "string"
},
"asm_group_id": {
"description": "The unsubscribe group associated with this email.",
"minimum": 1,
"type": "integer"
},
"categories": {
"description": "Categories users associated to the message",
"items": {
"type": "string"
},
"type": "array"
},
"events": {
"description": "List of events related to email message",
"items": {
"example": {
"bounce_type": "soft",
"event_name": "bounced",
"http_user_agent": "in tempor ex dolore est",
"mx_server": "quis proident",
"processed": "2017-10-13T18:56:21Z",
"reason": "some reason",
"url": "http://3LX,MU}N=B8'd,K}>bEma{l~!ad%peIF}y>qHfLPWQ$l9b\\!6.1H?$Z9H\"il-_gZD>/JPYsGqH4x4_3v090TCtnFalXGFiAdooDxgrDAYNXShUywSxwYr8gKeyc/4sal4VJ3IxEWsG74V5MYQ0mz27jhy7n5DHsUtApQ6zXHS13uO5vYBlJHpJRfuT6/F5nIpkHre2w3eTtN7M6pg9V5stjnnsavKkzQxyTv15CMSDLFwR_BTZwofhWpyBU7B9ypYL79vT97N3LDZyoaM/fNsOLPIqfGBer_Mx9_StergbQYANyOmOSjR6pZof01ky/ZcNDhpu3CkSl4MTtQ3NMCX780pOKQ5SYIPigyvz9IC9WtrCNcOkTxdOPdY0_4MJU4EuTTPmGvO/14KaJCDjIjgrbIqpzuUEL5mET0t2VeVlwvtnOnlHaBE8sic20ze2E0Xt3ETqXyzVJRjLDKh/LWkW8OVp_xkLBCCW7LQngRukKcOiWjMXeCEhYI9HoZ0RsMEWZC8KzRaHc4OI0uXPD4M9pav1LGrI/_0t_RnBnfnqGKsBJr0kdQi/Y6QN_aeawIqX5hDNIU3MF/wWKVWLS0ZFbDfK6KVv5oAid83EpwKoazAMA8MTfEXvHQLO7k7XYWX1Il3eGXL6/wCA96I1SOabzJkZHo2HsFpIC/VBk52Lnpp0xtDH/OCdlQ5e4PpxXQeklp70LPOndr7QKSYEQNUc48n36ixvTjhgpgO8wHsFFYqGcuBMHg9oaCARppQomiQDWYuVPVDynJHdsM1_gWl4/NSs8Y9PL7DrQXOu0UiFRRE0TUsvgqyUgJzlGjUnRziyYeROO75D0K_3aTtbGbCmhaxecos40a1w0PDCNkFp1W/iHwY7922drhsoM6ShwqqwGpAh5HLuU6Q5gqyckeai6YN7HCh9DdHPhhJcatgtMHZDKfQUBVt9ecUlDgiCFF_OnRX/GpzttcsL8E2FoXL9_eAWvSqjodROqx7MZCA/ORdnR/IssPCYP1kTHTIL5mZxv4UGEpyNjUzt4GdSJJTm0nztltWDYX8_Ezl2JvpLVnGVTJxobb4yQIJhe3n64khbOFyFLKHWEniIolm/AxpZQYmseWlVqrIz3YXU59XaSbTTrdCHNhvwF1ogXiiggN6TZ2B3QY_mBEtAp/SD0ONPVqEUkTNAFWTgnnlv6ZIMdMbTw5uZwtFRlB7qDvQouml9kujGmRu6k7zZMTOwWowRNtpboLUcL2NzkVgK6N1Zi2vq/Nt4NJvM5_l1dpIIbwJv_CIcZQZOqPtRWULa2iVxfmJJQaqgLQPwSHQH1zuRJMhraEsPjqVQRC0pZpSt/24VBDN8y31Ye/y_ekWxMdZCvr978C/WrdcTi29kxjJLyT9BII7BsgT5vLuI2l7ntqRAhAUWMs/h9JR0i8RbX5OfB46q41/TfmSdgi97bCR2HfgflyypXwKhRfKYU2MVpu2Dd90WQUlm7hZV8dSfGusuMj/nPMpRVWcbnvlAdsehJCPbLv6n4qdLSPeoMBo32acAGgu1BwBG8JsBgbH43yYi5X7UdGRWKqm_ZbqaDEKH3ncU/uA8EOJb41VfGho4LUeOi1IeYwVAhFEyO6YbteYZecEubrNFZrWWjZUqhzouzY95TeWU8E4StCXVPKlYPiFiwUSX20kG0lVtDbAy/7u4f4x0cYlFOvI1UN1qoOExmNxnxzQQFeM5exWfW2JrRXq5e0UdAJr4q2o9Y_0WaGfhL/nP6Ei06YajDKr11dK5H0LX/9CGTC37HFZeopyopzP_7fvGFkqIRoGTS48pLaIFz3gwpQNlWXUFCsd/PnRlsqJ3SBQSgp_AQe2cP6iBNy2bJI8lkxwY5YVDDdjxusuCcafdjfs2aUa/4tr_iMnNBnd27GxjQI28_JGJlfbOaajVJOxuPMT4ELpYCfPiFjdSbJyE0/gCwtj0rgDKSLWJnOPJ5TAJ935gCqeIsBhOhfcZX413GdilBZRRYEjCVKfOuWzHZ3GW/8yjyk5e_WMNv5F6xggl07w90DBwpx/Q/iWfncqMuSfoeFeqHQkDL9F5W19j1cGuAcyfIYMAXztHXpgTKh9vZcsLYC7LcgKr4FQj3JjEvtnDG2PjcMjGF/MnbCRCz22Ho410_vE9M1Hpq0wdk_i5DbZKNoSwlPgey9URkpuX146TcDdsx_VWDenCepY5HwMr9CPOY9hzUs/c5AWeUMXk/gvsI81Jkv5rHpEnNBUZXYzfqkwQfffhmrc/StLCtzRRlja8dpsEWdkzoKR9Kdxq1qAs5f0sdrGjVRLTT_s1Q2P59zhA/QmS4bubi64cYot3gSIgdNnkjA2GjCp1ETVa548_U9B6boTKDVmaKJlVIDvqL84RC3WI7Er/8opi2lZ48W83Ur47BRh38oOnI0agrCyZz8bp1w_gfVRlSO8PS0i/l_/qxq5xpLbhPkdxVoyZVsNAZchfnmkIHyIk5IK6EUDXdMR21y6OvKW50ZbooAtk9ymynBj4dAYMsd25RV7FE1I1vRTsiDw52/.E5WC0Ymo2zn.qelSbhzr-4laArYiWP.dwJB6qm_6rs0Rm5UXYaYtUNbh76_jJp_X1xQUCDSgbr2KOkDU0\"Q/-4dV\"Yk3QGg[(O86=Pf\"e17K4'r{)kicofHSXcMmP@>VF^`~4j4F*L/1]tD+Lw!WI!@]*OZm6C`M$u96}*ObEma{l~!ad%peIF}y>qHfLPWQ$l9b\\!6.1H?$Z9H\"il-_gZD>/JPYsGqH4x4_3v090TCtnFalXGFiAdooDxgrDAYNXShUywSxwYr8gKeyc/4sal4VJ3IxEWsG74V5MYQ0mz27jhy7n5DHsUtApQ6zXHS13uO5vYBlJHpJRfuT6/F5nIpkHre2w3eTtN7M6pg9V5stjnnsavKkzQxyTv15CMSDLFwR_BTZwofhWpyBU7B9ypYL79vT97N3LDZyoaM/fNsOLPIqfGBer_Mx9_StergbQYANyOmOSjR6pZof01ky/ZcNDhpu3CkSl4MTtQ3NMCX780pOKQ5SYIPigyvz9IC9WtrCNcOkTxdOPdY0_4MJU4EuTTPmGvO/14KaJCDjIjgrbIqpzuUEL5mET0t2VeVlwvtnOnlHaBE8sic20ze2E0Xt3ETqXyzVJRjLDKh/LWkW8OVp_xkLBCCW7LQngRukKcOiWjMXeCEhYI9HoZ0RsMEWZC8KzRaHc4OI0uXPD4M9pav1LGrI/_0t_RnBnfnqGKsBJr0kdQi/Y6QN_aeawIqX5hDNIU3MF/wWKVWLS0ZFbDfK6KVv5oAid83EpwKoazAMA8MTfEXvHQLO7k7XYWX1Il3eGXL6/wCA96I1SOabzJkZHo2HsFpIC/VBk52Lnpp0xtDH/OCdlQ5e4PpxXQeklp70LPOndr7QKSYEQNUc48n36ixvTjhgpgO8wHsFFYqGcuBMHg9oaCARppQomiQDWYuVPVDynJHdsM1_gWl4/NSs8Y9PL7DrQXOu0UiFRRE0TUsvgqyUgJzlGjUnRziyYeROO75D0K_3aTtbGbCmhaxecos40a1w0PDCNkFp1W/iHwY7922drhsoM6ShwqqwGpAh5HLuU6Q5gqyckeai6YN7HCh9DdHPhhJcatgtMHZDKfQUBVt9ecUlDgiCFF_OnRX/GpzttcsL8E2FoXL9_eAWvSqjodROqx7MZCA/ORdnR/IssPCYP1kTHTIL5mZxv4UGEpyNjUzt4GdSJJTm0nztltWDYX8_Ezl2JvpLVnGVTJxobb4yQIJhe3n64khbOFyFLKHWEniIolm/AxpZQYmseWlVqrIz3YXU59XaSbTTrdCHNhvwF1ogXiiggN6TZ2B3QY_mBEtAp/SD0ONPVqEUkTNAFWTgnnlv6ZIMdMbTw5uZwtFRlB7qDvQouml9kujGmRu6k7zZMTOwWowRNtpboLUcL2NzkVgK6N1Zi2vq/Nt4NJvM5_l1dpIIbwJv_CIcZQZOqPtRWULa2iVxfmJJQaqgLQPwSHQH1zuRJMhraEsPjqVQRC0pZpSt/24VBDN8y31Ye/y_ekWxMdZCvr978C/WrdcTi29kxjJLyT9BII7BsgT5vLuI2l7ntqRAhAUWMs/h9JR0i8RbX5OfB46q41/TfmSdgi97bCR2HfgflyypXwKhRfKYU2MVpu2Dd90WQUlm7hZV8dSfGusuMj/nPMpRVWcbnvlAdsehJCPbLv6n4qdLSPeoMBo32acAGgu1BwBG8JsBgbH43yYi5X7UdGRWKqm_ZbqaDEKH3ncU/uA8EOJb41VfGho4LUeOi1IeYwVAhFEyO6YbteYZecEubrNFZrWWjZUqhzouzY95TeWU8E4StCXVPKlYPiFiwUSX20kG0lVtDbAy/7u4f4x0cYlFOvI1UN1qoOExmNxnxzQQFeM5exWfW2JrRXq5e0UdAJr4q2o9Y_0WaGfhL/nP6Ei06YajDKr11dK5H0LX/9CGTC37HFZeopyopzP_7fvGFkqIRoGTS48pLaIFz3gwpQNlWXUFCsd/PnRlsqJ3SBQSgp_AQe2cP6iBNy2bJI8lkxwY5YVDDdjxusuCcafdjfs2aUa/4tr_iMnNBnd27GxjQI28_JGJlfbOaajVJOxuPMT4ELpYCfPiFjdSbJyE0/gCwtj0rgDKSLWJnOPJ5TAJ935gCqeIsBhOhfcZX413GdilBZRRYEjCVKfOuWzHZ3GW/8yjyk5e_WMNv5F6xggl07w90DBwpx/Q/iWfncqMuSfoeFeqHQkDL9F5W19j1cGuAcyfIYMAXztHXpgTKh9vZcsLYC7LcgKr4FQj3JjEvtnDG2PjcMjGF/MnbCRCz22Ho410_vE9M1Hpq0wdk_i5DbZKNoSwlPgey9URkpuX146TcDdsx_VWDenCepY5HwMr9CPOY9hzUs/c5AWeUMXk/gvsI81Jkv5rHpEnNBUZXYzfqkwQfffhmrc/StLCtzRRlja8dpsEWdkzoKR9Kdxq1qAs5f0sdrGjVRLTT_s1Q2P59zhA/QmS4bubi64cYot3gSIgdNnkjA2GjCp1ETVa548_U9B6boTKDVmaKJlVIDvqL84RC3WI7Er/8opi2lZ48W83Ur47BRh38oOnI0agrCyZz8bp1w_gfVRlSO8PS0i/l_/qxq5xpLbhPkdxVoyZVsNAZchfnmkIHyIk5IK6EUDXdMR21y6OvKW50ZbooAtk9ymynBj4dAYMsd25RV7FE1I1vRTsiDw52/.E5WC0Ymo2zn.qelSbhzr-4laArYiWP.dwJB6qm_6rs0Rm5UXYaYtUNbh76_jJp_X1xQUCDSgbr2KOkDU0\"Q/-4dV\"Yk3QGg[(O86=Pf\"e17K4'r{)kicofHSXcMmP@>VF^`~4j4F*L/1]tD+Lw!WI!@]*OZm6C`M$u96}*ObEma{l~!ad%peIF}y>qHfLPWQ$l9b\\!6.1H?$Z9H\"il-_gZD>/JPYsGqH4x4_3v090TCtnFalXGFiAdooDxgrDAYNXShUywSxwYr8gKeyc/4sal4VJ3IxEWsG74V5MYQ0mz27jhy7n5DHsUtApQ6zXHS13uO5vYBlJHpJRfuT6/F5nIpkHre2w3eTtN7M6pg9V5stjnnsavKkzQxyTv15CMSDLFwR_BTZwofhWpyBU7B9ypYL79vT97N3LDZyoaM/fNsOLPIqfGBer_Mx9_StergbQYANyOmOSjR6pZof01ky/ZcNDhpu3CkSl4MTtQ3NMCX780pOKQ5SYIPigyvz9IC9WtrCNcOkTxdOPdY0_4MJU4EuTTPmGvO/14KaJCDjIjgrbIqpzuUEL5mET0t2VeVlwvtnOnlHaBE8sic20ze2E0Xt3ETqXyzVJRjLDKh/LWkW8OVp_xkLBCCW7LQngRukKcOiWjMXeCEhYI9HoZ0RsMEWZC8KzRaHc4OI0uXPD4M9pav1LGrI/_0t_RnBnfnqGKsBJr0kdQi/Y6QN_aeawIqX5hDNIU3MF/wWKVWLS0ZFbDfK6KVv5oAid83EpwKoazAMA8MTfEXvHQLO7k7XYWX1Il3eGXL6/wCA96I1SOabzJkZHo2HsFpIC/VBk52Lnpp0xtDH/OCdlQ5e4PpxXQeklp70LPOndr7QKSYEQNUc48n36ixvTjhgpgO8wHsFFYqGcuBMHg9oaCARppQomiQDWYuVPVDynJHdsM1_gWl4/NSs8Y9PL7DrQXOu0UiFRRE0TUsvgqyUgJzlGjUnRziyYeROO75D0K_3aTtbGbCmhaxecos40a1w0PDCNkFp1W/iHwY7922drhsoM6ShwqqwGpAh5HLuU6Q5gqyckeai6YN7HCh9DdHPhhJcatgtMHZDKfQUBVt9ecUlDgiCFF_OnRX/GpzttcsL8E2FoXL9_eAWvSqjodROqx7MZCA/ORdnR/IssPCYP1kTHTIL5mZxv4UGEpyNjUzt4GdSJJTm0nztltWDYX8_Ezl2JvpLVnGVTJxobb4yQIJhe3n64khbOFyFLKHWEniIolm/AxpZQYmseWlVqrIz3YXU59XaSbTTrdCHNhvwF1ogXiiggN6TZ2B3QY_mBEtAp/SD0ONPVqEUkTNAFWTgnnlv6ZIMdMbTw5uZwtFRlB7qDvQouml9kujGmRu6k7zZMTOwWowRNtpboLUcL2NzkVgK6N1Zi2vq/Nt4NJvM5_l1dpIIbwJv_CIcZQZOqPtRWULa2iVxfmJJQaqgLQPwSHQH1zuRJMhraEsPjqVQRC0pZpSt/24VBDN8y31Ye/y_ekWxMdZCvr978C/WrdcTi29kxjJLyT9BII7BsgT5vLuI2l7ntqRAhAUWMs/h9JR0i8RbX5OfB46q41/TfmSdgi97bCR2HfgflyypXwKhRfKYU2MVpu2Dd90WQUlm7hZV8dSfGusuMj/nPMpRVWcbnvlAdsehJCPbLv6n4qdLSPeoMBo32acAGgu1BwBG8JsBgbH43yYi5X7UdGRWKqm_ZbqaDEKH3ncU/uA8EOJb41VfGho4LUeOi1IeYwVAhFEyO6YbteYZecEubrNFZrWWjZUqhzouzY95TeWU8E4StCXVPKlYPiFiwUSX20kG0lVtDbAy/7u4f4x0cYlFOvI1UN1qoOExmNxnxzQQFeM5exWfW2JrRXq5e0UdAJr4q2o9Y_0WaGfhL/nP6Ei06YajDKr11dK5H0LX/9CGTC37HFZeopyopzP_7fvGFkqIRoGTS48pLaIFz3gwpQNlWXUFCsd/PnRlsqJ3SBQSgp_AQe2cP6iBNy2bJI8lkxwY5YVDDdjxusuCcafdjfs2aUa/4tr_iMnNBnd27GxjQI28_JGJlfbOaajVJOxuPMT4ELpYCfPiFjdSbJyE0/gCwtj0rgDKSLWJnOPJ5TAJ935gCqeIsBhOhfcZX413GdilBZRRYEjCVKfOuWzHZ3GW/8yjyk5e_WMNv5F6xggl07w90DBwpx/Q/iWfncqMuSfoeFeqHQkDL9F5W19j1cGuAcyfIYMAXztHXpgTKh9vZcsLYC7LcgKr4FQj3JjEvtnDG2PjcMjGF/MnbCRCz22Ho410_vE9M1Hpq0wdk_i5DbZKNoSwlPgey9URkpuX146TcDdsx_VWDenCepY5HwMr9CPOY9hzUs/c5AWeUMXk/gvsI81Jkv5rHpEnNBUZXYzfqkwQfffhmrc/StLCtzRRlja8dpsEWdkzoKR9Kdxq1qAs5f0sdrGjVRLTT_s1Q2P59zhA/QmS4bubi64cYot3gSIgdNnkjA2GjCp1ETVa548_U9B6boTKDVmaKJlVIDvqL84RC3WI7Er/8opi2lZ48W83Ur47BRh38oOnI0agrCyZz8bp1w_gfVRlSO8PS0i/l_/qxq5xpLbhPkdxVoyZVsNAZchfnmkIHyIk5IK6EUDXdMR21y6OvKW50ZbooAtk9ymynBj4dAYMsd25RV7FE1I1vRTsiDw52/.E5WC0Ymo2zn.qelSbhzr-4laArYiWP.dwJB6qm_6rs0Rm5UXYaYtUNbh76_jJp_X1xQUCDSgbr2KOkDU0\"Q/-4dV\"Yk3QGg[(O86=Pf\"e17K4'r{)kicofHSXcMmP@>VF^`~4j4F*L/1]tD+Lw!WI!@]*OZm6C`M$u96}*O"
},
"properties": {
"enabled": {
"description": "Indicates if the certificate is enabled.",
"type": "boolean"
},
"integration_id": {
"description": "An ID that matches a certificate to a specific IdP integration. This is the `id` returned by the \"Get All SSO Integrations\" endpoint.",
"type": "string"
},
"public_certificate": {
"description": "This public certificate allows SendGrid to verify that SAML requests it receives are signed by an IdP that it recognizes.",
"type": "string"
}
},
"required": [
"public_certificate",
"integration_id"
],
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"id": 66138975,
"intergration_id": "b0b98502-9408-4b24-9e3d-31ed7cb15312",
"not_after": 1621289880,
"not_before": 1621289880,
"public_certificate": ""
}
}
},
"schema": {
"$ref": "#/components/schemas/sso-certificate-body"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_400"
},
"401": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_401"
},
"403": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_403"
},
"429": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_429"
},
"500": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_500"
}
},
"summary": "Create an SSO Certificate",
"tags": [
"Certificates"
],
"x-stoplight": {
"id": "POST_sso-certificates",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/sso/certificates/{cert_id}": {
"delete": {
"description": "**This endpoint allows you to delete an SSO certificate.**\n\nYou can retrieve a certificate's ID from the response provided by the \"Get All SSO Integrations\" endpoint.",
"operationId": "DELETE_sso-certificates-cert_id",
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"id": 66138975,
"intergration_id": "b0b98502-9408-4b24-9e3d-31ed7cb15312",
"not_after": 1621289880,
"not_before": 1621289880,
"public_certificate": ""
}
}
},
"schema": {
"$ref": "#/components/schemas/sso-certificate-body"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_400"
},
"401": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_401"
},
"403": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_403"
},
"429": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_429"
},
"500": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_500"
}
},
"summary": "Delete an SSO Certificate",
"tags": [
"Certificates"
],
"x-stoplight": {
"id": "DELETE_sso-certificates-certid",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve an individual SSO certificate.**",
"operationId": "GET_sso-certificates-cert_id",
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"id": 66138975,
"intergration_id": "b0b98502-9408-4b24-9e3d-31ed7cb15312",
"not_after": 1621289880,
"not_before": 1621289880,
"public_certificate": ""
}
}
},
"schema": {
"$ref": "#/components/schemas/sso-certificate-body"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_400"
},
"401": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_401"
},
"403": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_403"
},
"429": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_429"
},
"500": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_500"
}
},
"summary": "Get an SSO Certificate",
"tags": [
"Certificates"
],
"x-stoplight": {
"id": "GET_sso-certificates-certid",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"in": "path",
"name": "cert_id",
"required": true,
"schema": {
"type": "string"
}
}
],
"patch": {
"description": "**This endpoint allows you to update an existing certificate by ID.**\n\nYou can retrieve a certificate's ID from the response provided by the \"Get All SSO Integrations\" endpoint.",
"operationId": "PATCH_sso-certificates-cert_id",
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"enabled": false,
"intergration_id": "b0b98502-9408-4b24-9e3d-31ed7cb15312",
"public_certificate": ""
},
"properties": {
"enabled": {
"description": "Indicates whether or not the certificate is enabled.",
"type": "boolean"
},
"integration_id": {
"description": "An ID that matches a certificate to a specific IdP integration.",
"type": "string"
},
"public_certificate": {
"description": "This public certificate allows SendGrid to verify that SAML requests it receives are signed by an IdP that it recognizes.",
"type": "string"
}
},
"type": "object"
}
}
}
},
"responses": {
"400": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_400"
},
"401": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_401"
},
"403": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_403"
},
"429": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_429"
},
"500": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_500"
}
},
"summary": "Update SSO Certificate",
"tags": [
"Certificates"
],
"x-stoplight": {
"id": "PATCH_sso-certificates-certid",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/sso/integrations": {
"get": {
"description": "**This endpoint allows you to retrieve all SSO integrations tied to your Twilio SendGrid account.**\n\nThe IDs returned by this endpoint can be used by the APIs additional endpoints to modify your SSO integrations.",
"operationId": "GET_sso-integrations",
"parameters": [
{
"description": "If this parameter is set to `true`, the response will include the `completed_integration` field.",
"in": "query",
"name": "si",
"schema": {
"type": "boolean"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"audience_url": "https://api.sendgrid.com/v3/public/sso/saml/response/id/b0b98502-9408-4b24-9e3d-31ed7cb15312",
"completed_integration": true,
"enabled": true,
"entity_id": "http://www.okta.com/${org.externalKey}",
"id": "b0b98502-9408-4b24-9e3d-31ed7cb15312",
"last_updated": 1621288520,
"name": "Twilio SendGrid",
"signin_url": "https://example.okta.com/home/examplecompany/yokpGWsmpRUcuvXFb4x6/nfaVADNhuHvvReAEV4x6",
"signout_url": "https://example.okta.com/login/signout?fromURI=exampleappurl",
"single_signon_url": "https://api.sendgrid.com/v3/public/sso/saml/response/id/b0b98502-9408-4b24-9e3d-31ed7cb15312"
}
]
}
},
"schema": {
"items": {
"$ref": "#/components/schemas/sso-integration"
},
"type": "array"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_400"
},
"401": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_401"
},
"403": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_403"
},
"429": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_429"
},
"500": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_500"
}
},
"summary": "Get All SSO Integrations",
"tags": [
"Single Sign-On Settings"
],
"x-stoplight": {
"id": "GET_sso-integrations",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"post": {
"description": "**This endpoint allows you to create an SSO integration.**",
"operationId": "POST_sso-integrations",
"requestBody": {
"$ref": "#/components/requestBodies/create-integration-request"
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"enabled": true,
"entity_id": "http://www.okta.com/${org.externalKey}",
"last_updated": 1621288964,
"name": "Twilio SendGrid",
"signin_url": "https://example.okta.com/home/examplecompany/yokpGWsmpRUcuvXFb4x6/nfaVADNhuHvvReAEV4x6",
"signout_url": "https://example.okta.com/login/signout?fromURI=exampleappurl"
}
}
},
"schema": {
"$ref": "#/components/schemas/sso-integration"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_400"
},
"401": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_401"
},
"403": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_403"
},
"429": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_429"
},
"500": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_500"
}
},
"summary": "Create an SSO Integration",
"tags": [
"Single Sign-On Settings"
],
"x-stoplight": {
"id": "POST_sso-integrations",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/sso/integrations/{id}": {
"delete": {
"description": "**This endpoint allows you to delete an IdP configuration by ID.**\n\nYou can retrieve the IDs for your configurations from the response provided by the \"Get All SSO Integrations\" endpoint.",
"operationId": "DELETE_sso-integrations-id",
"responses": {
"204": {
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_400"
},
"401": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_401"
},
"403": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_403"
},
"429": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_429"
},
"500": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_500"
}
},
"summary": "Delete an SSO Integration",
"tags": [
"Single Sign-On Settings"
],
"x-stoplight": {
"id": "DELETE_sso-integrations-id",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve an SSO integration by ID.**\n\nYou can retrieve the IDs for your configurations from the response provided by the \"Get All SSO Integrations\" endpoint.",
"operationId": "GET_sso-integrations-id",
"parameters": [
{
"description": "If this parameter is set to `true`, the response will include the `completed_integration` field.",
"in": "query",
"name": "si",
"schema": {
"type": "boolean"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"audience_url": "https://api.sendgrid.com/v3/public/sso/saml/response/id/b0b98502-9408-4b24-9e3d-31ed7cb15312",
"completed_integration": true,
"enabled": true,
"entity_id": "http://www.okta.com/${org.externalKey}",
"id": "b0b98502-9408-4b24-9e3d-31ed7cb15312",
"last_updated": 1621288964,
"name": "Twilio SendGrid",
"signin_url": "https://example.okta.com/home/examplecompany/yokpGWsmpRUcuvXFb4x6/nfaVADNhuHvvReAEV4x6",
"signout_url": "https://example.okta.com/login/signout?fromURI=exampleappurl"
}
}
},
"schema": {
"$ref": "#/components/schemas/sso-integration"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_400"
},
"401": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_401"
},
"403": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_403"
},
"429": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_429"
},
"500": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_500"
}
},
"summary": "Get an SSO Integration",
"tags": [
"Single Sign-On Settings"
],
"x-stoplight": {
"id": "GET_sso-integrations-id",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"patch": {
"description": "**This endpoint allows you to modify an exisiting SSO integration.**\n\nYou can retrieve the IDs for your configurations from the response provided by the \"Get All SSO Integrations\" endpoint.",
"operationId": "PATCH_sso-integrations-id",
"parameters": [
{
"description": "If this parameter is set to `true`, the response will include the `completed_integration` field.",
"in": "query",
"name": "si",
"schema": {
"type": "boolean"
}
}
],
"requestBody": {
"$ref": "#/components/requestBodies/create-integration-request"
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"audience_url": "https://api.sendgrid.com/v3/public/sso/saml/response/id/b0b98502-9408-4b24-9e3d-31ed7cb15312",
"enabled": true,
"entity_id": "http://www.okta.com/${org.externalKey}",
"id": "b0b98502-9408-4b24-9e3d-31ed7cb15312",
"last_updated": 1621288964,
"name": "Twilio SendGrid",
"signin_url": "https://example.okta.com/home/examplecompany/yokpGWsmpRUcuvXFb4x6/nfaVADNhuHvvReAEV4x6",
"signout_url": "https://example.okta.com/login/signout?fromURI=exampleappurl",
"single_signon_url": "https://api.sendgrid.com/v3/public/sso/saml/response/id/b0b98502-9408-4b24-9e3d-31ed7cb15312"
}
}
},
"schema": {
"$ref": "#/components/schemas/sso-integration"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_400"
},
"401": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_401"
},
"403": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_403"
},
"429": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_429"
},
"500": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_500"
}
},
"summary": "Update an SSO Integration",
"tags": [
"Single Sign-On Settings"
],
"x-stoplight": {
"id": "PATCH_sso-integrations-id",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/sso/integrations/{integration_id}/certificates": {
"get": {
"description": "**This endpoint allows you to retrieve all your IdP configurations by configuration ID.**\n\nThe `integration_id` expected by this endpoint is the `id` returned in the response by the \"Get All SSO Integrations\" endpoint.",
"operationId": "GET_sso-integrations-integration_id-certificates",
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"id": 66138975,
"intergration_id": "b0b98502-9408-4b24-9e3d-31ed7cb15312",
"not_after": 1621289880,
"not_before": 1621289880,
"public_certificate": ""
}
]
}
},
"schema": {
"items": {
"$ref": "#/components/schemas/sso-certificate-body"
},
"type": "array"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_400"
},
"401": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_401"
},
"403": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_403"
},
"429": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_429"
},
"500": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_500"
}
},
"summary": "Get All SSO Certificates by Integration",
"tags": [
"Certificates"
],
"x-stoplight": {
"id": "GET_sso-integrations-integrationid-certificates",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"description": "An ID that matches a certificate to a specific IdP integration.",
"in": "path",
"name": "integration_id",
"required": true,
"schema": {
"type": "string"
}
}
]
},
"/sso/teammates": {
"post": {
"description": "**This endpoint allows you to create an SSO Teammate.**\n\nThe email provided for this user will also function as the Teammate’s username.",
"operationId": "POST_sso-teammates",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/sso-teammate-request"
}
}
}
},
"responses": {
"201": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"email": "jane_doe@example.com",
"first_name": "Jane",
"is_read_only": true,
"is_sso": true,
"last_name": "Doe",
"username": "jane_doe@example.com"
}
}
},
"schema": {
"$ref": "#/components/schemas/sso-teammate-response"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_400"
},
"401": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_401"
},
"403": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_403"
},
"429": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_429"
},
"500": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_500"
}
},
"summary": "Create SSO Teammate",
"tags": [
"Single Sign-On Teammates"
],
"x-stoplight": {
"id": "POST_sso-teammates",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/sso/teammates/{username}": {
"parameters": [
{
"description": "This email address must be the same address assigned to the teammate in your IdP",
"in": "path",
"name": "username",
"required": true,
"schema": {
"format": "email",
"type": "string"
}
}
],
"patch": {
"description": "**This endpoint allows you to modify an existing SSO Teammate.**\n\nTo turn a teammate into an admin, the request body should contain the `is_admin` field set to `true`. Otherwise, set `is_admin` to false and pass in all the scopes that a teammate should have.\n\nOnly the parent user and Teammates with admin permissions can update another Teammate’s permissions. Admin users can only update permissions.",
"operationId": "PATCH_sso-teammates-username",
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"email": "jane_doe@example.com",
"first_name": "Jane",
"is_admin": false,
"last_name": "Doe",
"scopes": [
"mail.batch.create",
"mail.batch.delete",
"mail.batch.read",
"mail.batch.update",
"mail.send"
]
},
"properties": {
"first_name": {
"type": "string"
},
"is_admin": {
"type": "boolean"
},
"last_name": {
"type": "string"
},
"scopes": {
"items": {
"type": "string"
},
"type": "array"
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"Country": "United States",
"address": "1234 Fake St.",
"address2": "Suite 5",
"city": "San Francisco",
"email": "jane_doe@example.com",
"first_name": "Jane",
"is_admin": false,
"is_sso": true,
"last_name": "Doe",
"phone": "+15555555555",
"state": "CA",
"user_type": "teammate",
"username": "jane_doe@example.com",
"zip": "94105"
}
}
},
"schema": {
"$ref": "#/components/schemas/sso-teammates-patch-response"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_400"
},
"401": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_401"
},
"403": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_403"
},
"429": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_429"
},
"500": {
"$ref": "#/components/responses/trait_singleSignOnErrorsTrait_500"
}
},
"summary": "Edit an SSO Teammate",
"tags": [
"Single Sign-On Teammates"
],
"x-stoplight": {
"id": "PATCH_sso-teammates-username",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/stats": {
"get": {
"description": "**This endpoint allows you to retrieve all of your global email statistics between a given date range.**\n\nParent accounts will see aggregated stats for their account and all subuser accounts. Subuser accounts will only see their own stats.",
"operationId": "GET_stats",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
},
{
"$ref": "#/components/parameters/trait_statsAdvancedQueryStringsLimitOffset_limit"
},
{
"$ref": "#/components/parameters/trait_statsAdvancedQueryStringsLimitOffset_offset"
},
{
"$ref": "#/components/parameters/trait_statsAdvancedQueryStringsLimitOffset_aggregated_by"
},
{
"$ref": "#/components/parameters/trait_statsAdvancedQueryStringsLimitOffset_start_date"
},
{
"$ref": "#/components/parameters/trait_statsAdvancedQueryStringsLimitOffset_end_date"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"date": "2015-11-03",
"stats": [
{
"metrics": {
"blocks": 0,
"bounce_drops": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"invalid_emails": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_report_drops": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0,
"unsubscribe_drops": 0,
"unsubscribes": 0
}
}
]
},
{
"date": "2015-11-04",
"stats": [
{
"metrics": {
"blocks": 0,
"bounce_drops": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"invalid_emails": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_report_drops": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0,
"unsubscribe_drops": 0,
"unsubscribes": 0
}
}
]
},
{
"date": "2015-11-05",
"stats": [
{
"metrics": {
"blocks": 0,
"bounce_drops": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"invalid_emails": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_report_drops": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0,
"unsubscribe_drops": 0,
"unsubscribes": 0
}
}
]
},
{
"date": "2015-11-06",
"stats": [
{
"metrics": {
"blocks": 0,
"bounce_drops": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"invalid_emails": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_report_drops": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0,
"unsubscribe_drops": 0,
"unsubscribes": 0
}
}
]
},
{
"date": "2015-11-07",
"stats": [
{
"metrics": {
"blocks": 0,
"bounce_drops": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"invalid_emails": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_report_drops": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0,
"unsubscribe_drops": 0,
"unsubscribes": 0
}
}
]
},
{
"date": "2015-11-08",
"stats": [
{
"metrics": {
"blocks": 0,
"bounce_drops": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"invalid_emails": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_report_drops": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0,
"unsubscribe_drops": 0,
"unsubscribes": 0
}
}
]
},
{
"date": "2015-11-09",
"stats": [
{
"metrics": {
"blocks": 0,
"bounce_drops": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"invalid_emails": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_report_drops": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0,
"unsubscribe_drops": 0,
"unsubscribes": 0
}
}
]
}
]
}
},
"schema": {
"items": {
"properties": {
"date": {
"description": "The date the stats were gathered.",
"type": "string"
},
"stats": {
"description": "The individual email activity stats.",
"items": {
"properties": {
"metrics": {
"$ref": "#/components/schemas/stats-advanced-global-stats"
}
},
"type": "object"
},
"type": "array"
}
},
"required": [
"date",
"stats"
],
"type": "object"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve global email statistics",
"tags": [
"Stats"
],
"x-stoplight": {
"id": "GET_stats",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/subusers": {
"get": {
"description": "**This endpoint allows you to retrieve a list of all of your subusers.**\n\nYou can choose to retrieve specific subusers as well as limit the results that come back from the API.",
"operationId": "GET_subusers",
"parameters": [
{
"description": "The username of this subuser.",
"in": "query",
"name": "username",
"schema": {
"type": "string"
}
},
{
"description": "The number of results you would like to get in each request.",
"in": "query",
"name": "limit",
"schema": {
"type": "integer"
}
},
{
"description": "The number of subusers to skip.",
"in": "query",
"name": "offset",
"schema": {
"type": "integer"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"disabled": false,
"email": "example@example.com",
"id": 1234,
"username": "example_subuser"
},
{
"disabled": false,
"email": "example2@example.com",
"id": 1234,
"username": "example_subuser2"
}
]
}
},
"schema": {
"items": {
"$ref": "#/components/schemas/subuser"
},
"type": "array"
}
}
},
"description": ""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": "Unexpected error in API call. See HTTP response body for details."
}
},
"security": [
{
"Authorization": []
}
],
"summary": "List all Subusers",
"tags": [
"Subusers API"
],
"x-stoplight": {
"id": "GET_subusers",
"mock": {
"dynamic": false
},
"public": true
}
},
"post": {
"description": "**This endpoint allows you to create a new subuser.**",
"operationId": "POST_subusers",
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"email": "John@example.com",
"ips": [
"1.1.1.1",
"2.2.2.2"
],
"password": "johns_password",
"username": "John@example.com"
},
"properties": {
"email": {
"description": "The email address of the subuser.",
"format": "email",
"type": "string"
},
"ips": {
"description": "The IP addresses that should be assigned to this subuser.",
"items": {
"format": "ipv4",
"type": "string"
},
"type": "array"
},
"password": {
"description": "The password this subuser will use when logging into SendGrid.",
"type": "string"
},
"username": {
"description": "The username for this subuser.",
"type": "string"
}
},
"required": [
"username",
"email",
"password",
"ips"
],
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"authorization_token": "",
"credit_allocation": {
"type": "unlimited"
},
"email": "example@example.com",
"signup_session_token": "",
"user_id": 1234,
"username": "example_subuser"
}
}
},
"schema": {
"$ref": "#/components/schemas/subuser_post"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"message": "username exists"
},
{
"message": "unable to validate IPs at this time"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"403": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"message": "you dont have permission to access this resource"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"500": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"message": "unable to validate IPs at this time"
}
]
}
}
},
"schema": {
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Create Subuser",
"tags": [
"Subusers API"
],
"x-stoplight": {
"id": "POST_subusers",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/subusers/reputations": {
"get": {
"description": "**This endpoint allows you to request the reputations for your subusers.**\n\nSubuser sender reputations give a good idea how well a sender is doing with regards to how recipients and recipient servers react to the mail that is being received. When a bounce, spam report, or other negative action happens on a sent email, it will affect your sender rating.",
"operationId": "GET_subusers-reputations",
"parameters": [
{
"in": "query",
"name": "usernames",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"reputation": 99,
"username": "example_subuser"
},
{
"reputation": 95.2,
"username": "example_subuser2"
}
]
}
},
"schema": {
"items": {
"properties": {
"reputation": {
"description": "The sender reputation this subuser has attained.",
"type": "number"
},
"username": {
"description": "The subuser that has this reputation.f",
"type": "string"
}
},
"required": [
"reputation",
"username"
],
"type": "object"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve Subuser Reputations",
"tags": [
"Subusers API"
],
"x-stoplight": {
"id": "GET_subusers-reputations",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/subusers/stats": {
"get": {
"description": "**This endpoint allows you to retrieve the email statistics for the given subusers.**\n\nYou may retrieve statistics for up to 10 different subusers by including an additional _subusers_ parameter for each additional subuser.",
"operationId": "GET_subusers-stats",
"parameters": [
{
"description": "Limits the number of results returned per page.",
"in": "query",
"name": "limit",
"required": false,
"schema": {
"type": "integer"
}
},
{
"description": "The point in the list to begin retrieving results from.",
"in": "query",
"name": "offset",
"required": false,
"schema": {
"type": "integer"
}
},
{
"description": "How to group the statistics. Must be either \"day\", \"week\", or \"month\".",
"in": "query",
"name": "aggregated_by",
"required": false,
"schema": {
"enum": [
"day",
"week",
"month"
],
"type": "string"
}
},
{
"description": "The subuser you want to retrieve statistics for. You may include this parameter up to 10 times to retrieve statistics for multiple subusers.",
"in": "query",
"name": "subusers",
"required": true,
"schema": {
"type": "string"
}
},
{
"description": "The starting date of the statistics to retrieve. Must follow format YYYY-MM-DD.",
"in": "query",
"name": "start_date",
"required": true,
"schema": {
"type": "string"
}
},
{
"description": "The end date of the statistics to retrieve. Defaults to today.",
"in": "query",
"name": "end_date",
"required": false,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"date": "2015-10-01",
"stats": [
{
"metrics": {
"blocks": 0,
"bounce_drops": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"invalid_emails": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_report_drops": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0,
"unsubscribe_drops": 0,
"unsubscribes": 0
},
"name": "Matt_subuser",
"type": "subuser"
}
]
},
{
"date": "2015-10-02",
"stats": [
{
"metrics": {
"blocks": 0,
"bounce_drops": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"invalid_emails": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_report_drops": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0,
"unsubscribe_drops": 0,
"unsubscribes": 0
},
"name": "Matt_subuser",
"type": "subuser"
}
]
},
{
"date": "2015-10-03",
"stats": [
{
"metrics": {
"blocks": 0,
"bounce_drops": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"invalid_emails": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_report_drops": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0,
"unsubscribe_drops": 0,
"unsubscribes": 0
},
"name": "Matt_subuser",
"type": "subuser"
}
]
},
{
"date": "2015-10-04",
"stats": [
{
"metrics": {
"blocks": 0,
"bounce_drops": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"invalid_emails": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_report_drops": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0,
"unsubscribe_drops": 0,
"unsubscribes": 0
},
"name": "Matt_subuser",
"type": "subuser"
}
]
},
{
"date": "2015-10-05",
"stats": [
{
"metrics": {
"blocks": 0,
"bounce_drops": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"invalid_emails": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_report_drops": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0,
"unsubscribe_drops": 0,
"unsubscribes": 0
},
"name": "Matt_subuser",
"type": "subuser"
}
]
},
{
"date": "2015-10-06",
"stats": [
{
"metrics": {
"blocks": 0,
"bounce_drops": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"invalid_emails": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_report_drops": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0,
"unsubscribe_drops": 0,
"unsubscribes": 0
},
"name": "Matt_subuser",
"type": "subuser"
}
]
},
{
"date": "2015-10-07",
"stats": [
{
"metrics": {
"blocks": 0,
"bounce_drops": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"invalid_emails": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_report_drops": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0,
"unsubscribe_drops": 0,
"unsubscribes": 0
},
"name": "Matt_subuser",
"type": "subuser"
}
]
},
{
"date": "2015-10-08",
"stats": [
{
"metrics": {
"blocks": 0,
"bounce_drops": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"invalid_emails": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_report_drops": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0,
"unsubscribe_drops": 0,
"unsubscribes": 0
},
"name": "Matt_subuser",
"type": "subuser"
}
]
},
{
"date": "2015-10-09",
"stats": [
{
"metrics": {
"blocks": 0,
"bounce_drops": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"invalid_emails": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_report_drops": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0,
"unsubscribe_drops": 0,
"unsubscribes": 0
},
"name": "Matt_subuser",
"type": "subuser"
}
]
},
{
"date": "2015-10-10",
"stats": [
{
"metrics": {
"blocks": 0,
"bounce_drops": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"invalid_emails": 0,
"opens": 0,
"processed": 0,
"requests": 0,
"spam_report_drops": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0,
"unsubscribe_drops": 0,
"unsubscribes": 0
},
"name": "Matt_subuser",
"type": "subuser"
}
]
}
]
}
},
"schema": {
"items": {
"$ref": "#/components/schemas/category_stats"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve email statistics for your subusers.",
"tags": [
"Subuser Statistics"
],
"x-stoplight": {
"id": "GET_subusers-stats",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/subusers/stats/monthly": {
"get": {
"description": "**This endpoint allows you to retrieve the monthly email statistics for all subusers over the given date range.**\n\nWhen using the `sort_by_metric` to sort your stats by a specific metric, you can not sort by the following metrics:\n`bounce_drops`, `deferred`, `invalid_emails`, `processed`, `spam_report_drops`, `spam_reports`, or `unsubscribe_drops`.",
"operationId": "GET_subusers-stats-monthly",
"parameters": [
{
"description": "The date of the month to retrieve statistics for. Must be formatted YYYY-MM-DD",
"in": "query",
"name": "date",
"required": true,
"schema": {
"type": "string"
}
},
{
"description": "A substring search of your subusers.",
"in": "query",
"name": "subuser",
"required": false,
"schema": {
"type": "string"
}
},
{
"description": "The metric that you want to sort by. Metrics that you can sort by are: `blocks`, `bounces`, `clicks`, `delivered`, `opens`, `requests`, `unique_clicks`, `unique_opens`, and `unsubscribes`.'",
"in": "query",
"name": "sort_by_metric",
"required": false,
"schema": {
"default": "delivered",
"enum": [
"blocks",
"bounces",
"clicks",
"delivered",
"opens",
"requests",
"unique_clicks",
"unique_opens",
"unsubscribes"
],
"type": "string"
}
},
{
"description": "The direction you want to sort.",
"in": "query",
"name": "sort_by_direction",
"required": false,
"schema": {
"default": "desc",
"enum": [
"desc",
"asc"
],
"type": "string"
}
},
{
"description": "Optional field to limit the number of results returned.",
"in": "query",
"name": "limit",
"required": false,
"schema": {
"default": 5,
"type": "integer"
}
},
{
"description": "Optional beginning point in the list to retrieve from.",
"in": "query",
"name": "offset",
"required": false,
"schema": {
"default": 0,
"type": "integer"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"date": "2016-02-01",
"stats": [
{
"first_name": "John",
"last_name": "Doe",
"metrics": {
"blocks": 0,
"bounce_drops": 0,
"bounces": 0,
"clicks": 0,
"deferred": 0,
"delivered": 0,
"invalid_emails": 0,
"opens": 1,
"processed": 0,
"requests": 100,
"spam_report_drops": 0,
"spam_reports": 99,
"unique_clicks": 0,
"unique_opens": 0,
"unsubscribe_drops": 0,
"unsubscribes": 0
},
"name": "user1",
"type": "subuser"
},
{
"first_name": "Jane",
"last_name": "Doe",
"metrics": {
"blocks": 0,
"bounce_drops": 0,
"bounces": 0,
"clicks": 5,
"deferred": 0,
"delivered": 0,
"invalid_emails": 0,
"opens": 10,
"processed": 10,
"requests": 10,
"spam_report_drops": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0,
"unsubscribe_drops": 0,
"unsubscribes": 0
},
"name": "user2",
"type": "subuser"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/subuser_stats"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve monthly stats for all subusers",
"tags": [
"Subuser Statistics"
],
"x-stoplight": {
"id": "GET_subusers-stats-monthly",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/subusers/stats/sums": {
"get": {
"description": "**This endpoint allows you to retrieve the total sums of each email statistic metric for all subusers over the given date range.**",
"operationId": "GET_subusers-stats-sums",
"parameters": [
{
"description": "The direction you want to sort. ",
"in": "query",
"name": "sort_by_direction",
"required": false,
"schema": {
"default": "desc",
"enum": [
"desc",
"asc"
],
"type": "string"
}
},
{
"description": "The starting date of the statistics to retrieve. Must follow format YYYY-MM-DD.",
"in": "query",
"name": "start_date",
"required": true,
"schema": {
"type": "string"
}
},
{
"description": "The end date of the statistics to retrieve. Defaults to today. Must follow format YYYY-MM-DD.",
"in": "query",
"name": "end_date",
"required": false,
"schema": {
"type": "string"
}
},
{
"description": "Limits the number of results returned per page.",
"in": "query",
"name": "limit",
"required": false,
"schema": {
"default": 5,
"type": "integer"
}
},
{
"description": "The point in the list to begin retrieving results from.",
"in": "query",
"name": "offset",
"required": false,
"schema": {
"default": 0,
"type": "integer"
}
},
{
"description": "How to group the statistics. Defaults to today. Must follow format YYYY-MM-DD.",
"in": "query",
"name": "aggregated_by",
"required": false,
"schema": {
"type": "string"
}
},
{
"description": "The metric that you want to sort by. Must be a single metric.",
"in": "query",
"name": "sort_by_metric",
"required": false,
"schema": {
"default": "delivered",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"date": "2015-10-11",
"stats": []
}
}
},
"schema": {
"$ref": "#/components/schemas/category_stats"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve the totals for each email statistic metric for all subusers.",
"tags": [
"Subuser Statistics"
],
"x-stoplight": {
"id": "GET_subusers-stats-sums",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/subusers/{subuser_name}": {
"delete": {
"description": "**This endpoint allows you to delete a subuser.**\n\nThis is a permanent action. Once deleted, a subuser cannot be retrieved.",
"operationId": "DELETE_subusers-subuser_name",
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"properties": {},
"type": "object"
}
}
},
"description": ""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete a subuser",
"tags": [
"Subusers API"
],
"x-stoplight": {
"id": "DELETE_subusers-subusername",
"mock": {
"dynamic": false
},
"public": true
}
},
"parameters": [
{
"in": "path",
"name": "subuser_name",
"required": true,
"schema": {
"type": "string"
}
}
],
"patch": {
"description": "**This endpoint allows you to enable or disable a subuser.**",
"operationId": "PATCH_subusers-subuser_name",
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"disabled": false
},
"properties": {
"disabled": {
"description": "Whether or not this subuser is disabled. True means disabled, False means enabled.",
"type": "boolean"
}
},
"type": "object"
}
}
}
},
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"properties": {},
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"message": "invalid username"
},
{
"message": "no fields provided"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"500": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"message": "unable to enable user"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Enable/disable a subuser",
"tags": [
"Subusers API"
],
"x-stoplight": {
"id": "PATCH_subusers-subusername",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/subusers/{subuser_name}/ips": {
"parameters": [
{
"in": "path",
"name": "subuser_name",
"required": true,
"schema": {
"type": "string"
}
}
],
"put": {
"description": "**This endpoint allows you update your subusers' assigned IP.**\n\nEach subuser should be assigned to an IP address from which all of this subuser's mail will be sent. Often, this is the same IP as the parent account, but each subuser can have one or more of their own IP addresses as well. \n\nMore information:\n\n* [How to request more IPs](https://sendgrid.com/docs/ui/account-and-settings/dedicated-ip-addresses/)\n* [Setup Reverse DNS](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-reverse-dns/)",
"operationId": "PUT_subusers-subuser_name-ips",
"requestBody": {
"content": {
"application/json": {
"schema": {
"description": "The IP addresses you would like to assign to the subuser.",
"example": [
"127.0.0.1"
],
"items": {
"format": "ipv4",
"type": "string"
},
"type": "array"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"ips": [
"127.0.0.1"
]
}
}
},
"schema": {
"properties": {
"ips": {
"description": "The IP addresses that are assigned to the subuser.",
"items": {
"format": "ipv4",
"type": "string"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update IPs assigned to a subuser",
"tags": [
"Subusers API"
],
"x-stoplight": {
"id": "PUT_subusers-subusername-ips",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/subusers/{subuser_name}/monitor": {
"delete": {
"operationId": "DELETE_subusers-subuser_name-monitor",
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"properties": {},
"type": "object"
}
}
},
"description": ""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "No monitor settings for this user"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete monitor settings",
"tags": [
"Subuser Monitor Settings"
],
"x-stoplight": {
"id": "DELETE_subusers-subusername-monitor",
"mock": {
"dynamic": false
},
"public": true
}
},
"get": {
"operationId": "GET_subusers-subuser_name-monitor",
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"email": "example@example.com",
"frequency": 500
}
}
},
"schema": {
"$ref": "#/components/schemas/monitor"
}
}
},
"description": ""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "No monitor settings for this user"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve monitor settings for a subuser",
"tags": [
"Subuser Monitor Settings"
],
"x-stoplight": {
"id": "GET_subusers-subusername-monitor",
"mock": {
"dynamic": false
},
"public": true
}
},
"parameters": [
{
"description": "The name of the subuser for which to retrieve monitor settings.",
"in": "path",
"name": "subuser_name",
"required": true,
"schema": {
"type": "string"
}
}
],
"post": {
"operationId": "POST_subusers-subuser_name-monitor",
"requestBody": {
"$ref": "#/components/requestBodies/monitor"
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"email": "example@example.com",
"frequency": 50000
}
}
},
"schema": {
"$ref": "#/components/schemas/monitor"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "User already has a monitor"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Create monitor settings",
"tags": [
"Subuser Monitor Settings"
],
"x-stoplight": {
"id": "POST_subusers-subusername-monitor",
"mock": {
"dynamic": false
},
"public": true
}
},
"put": {
"operationId": "PUT_subusers-subuser_name-monitor",
"requestBody": {
"$ref": "#/components/requestBodies/monitor"
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"email": "example@example.com",
"frequency": 500
}
}
},
"schema": {
"$ref": "#/components/schemas/monitor"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": "email",
"message": "Email is required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update Monitor Settings for a subuser",
"tags": [
"Subuser Monitor Settings"
],
"x-stoplight": {
"id": "PUT_subusers-subusername-monitor",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/subusers/{subuser_name}/stats/monthly": {
"get": {
"description": "**This endpoint allows you to retrive the monthly email statistics for a specific subuser.**\n\nWhen using the `sort_by_metric` to sort your stats by a specific metric, you can not sort by the following metrics:\n`bounce_drops`, `deferred`, `invalid_emails`, `processed`, `spam_report_drops`, `spam_reports`, or `unsubscribe_drops`.",
"operationId": "GET_subusers-subuser_name-stats-monthly",
"parameters": [
{
"description": "The date of the month to retrieve statistics for. Must be formatted YYYY-MM-DD",
"in": "query",
"name": "date",
"required": true,
"schema": {
"type": "string"
}
},
{
"description": "The metric that you want to sort by. Metrics that you can sort by are: `blocks`, `bounces`, `clicks`, `delivered`, `opens`, `requests`, `unique_clicks`, `unique_opens`, and `unsubscribes`.'",
"in": "query",
"name": "sort_by_metric",
"required": false,
"schema": {
"default": "delivered",
"type": "string"
}
},
{
"description": "The direction you want to sort.",
"in": "query",
"name": "sort_by_direction",
"required": false,
"schema": {
"default": "desc",
"enum": [
"desc",
"asc"
],
"type": "string"
}
},
{
"description": "Optional field to limit the number of results returned.",
"in": "query",
"name": "limit",
"required": false,
"schema": {
"default": 5,
"type": "integer"
}
},
{
"description": "Optional beginning point in the list to retrieve from.",
"in": "query",
"name": "offset",
"required": false,
"schema": {
"default": 0,
"type": "integer"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"date": "2016-02-01",
"stats": [
{
"first_name": "John",
"last_name": "Doe",
"metrics": {
"blocks": 0,
"bounce_drops": 0,
"bounces": 0,
"clicks": 5,
"deferred": 0,
"delivered": 0,
"invalid_emails": 0,
"opens": 10,
"processed": 10,
"requests": 10,
"spam_report_drops": 0,
"spam_reports": 0,
"unique_clicks": 0,
"unique_opens": 0,
"unsubscribe_drops": 0,
"unsubscribes": 0
},
"name": "user1",
"type": "subuser"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/subuser_stats"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve the monthly email statistics for a single subuser",
"tags": [
"Subuser Statistics"
],
"x-stoplight": {
"id": "GET_subusers-subusername-stats-monthly",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"in": "path",
"name": "subuser_name",
"required": true,
"schema": {
"type": "string"
}
}
]
},
"/suppression/blocks": {
"delete": {
"description": "**This endpoint allows you to delete all email addresses on your blocks list.**\n\nThere are two options for deleting blocked emails: \n\n1. You can delete all blocked emails by setting `delete_all` to `true` in the request body. \n2. You can delete a selection of blocked emails by specifying the email addresses in the `emails` array of the request body.",
"operationId": "DELETE_suppression-blocks",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"delete_all": false,
"emails": [
"example1@example.com",
"example2@example.com"
]
},
"properties": {
"delete_all": {
"description": "Indicates if you want to delete all blocked email addresses.",
"type": "boolean"
},
"emails": {
"description": "The specific blocked email addresses that you want to delete.",
"items": {
"type": "string"
},
"type": "array"
}
},
"type": "object"
}
}
}
},
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"properties": {},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete blocks",
"tags": [
"Blocks API"
],
"x-stoplight": {
"id": "DELETE_suppression-blocks",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve all email addresses that are currently on your blocks list.**",
"operationId": "GET_suppression-blocks",
"parameters": [
{
"description": "The start of the time range when a blocked email was created (inclusive). This is a unix timestamp.",
"in": "query",
"name": "start_time",
"schema": {
"type": "integer"
}
},
{
"description": "The end of the time range when a blocked email was created (inclusive). This is a unix timestamp.",
"in": "query",
"name": "end_time",
"schema": {
"type": "integer"
}
},
{
"description": "Limit the number of results to be displayed per page.",
"in": "query",
"name": "limit",
"schema": {
"type": "integer"
}
},
{
"description": "The point in the list to begin displaying results.",
"in": "query",
"name": "offset",
"schema": {
"type": "integer"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"created": 1443651154,
"email": "example@example.com",
"reason": "error dialing remote address: dial tcp 10.57.152.165:25: no route to host",
"status": "4.0.0"
},
{
"created": 1443651155,
"email": "example1@example.com",
"reason": "unable to resolve MX record for example.com: servfail",
"status": "4.0.0"
}
]
}
},
"schema": {
"$ref": "#/components/schemas/blocks-response"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve all blocks",
"tags": [
"Blocks API"
],
"x-stoplight": {
"id": "GET_suppression-blocks",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/suppression/blocks/{email}": {
"delete": {
"description": "**This endpoint allows you to delete a specific email address from your blocks list.**",
"operationId": "DELETE_suppression-blocks-email",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"properties": {},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete a specific block",
"tags": [
"Blocks API"
],
"x-stoplight": {
"id": "DELETE_suppression-blocks-email",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve a specific email address from your blocks list.**",
"operationId": "GET_suppression-blocks-email",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"created": 1443651154,
"email": "example@example.com",
"reason": "error dialing remote address: dial tcp 10.57.152.165:25: no route to host",
"status": "4.0.0"
}
]
}
},
"schema": {
"$ref": "#/components/schemas/blocks-response"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve a specific block",
"tags": [
"Blocks API"
],
"x-stoplight": {
"id": "GET_suppression-blocks-email",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"description": "The email address of the specific block.",
"in": "path",
"name": "email",
"required": true,
"schema": {
"format": "email",
"type": "string"
}
}
]
},
"/suppression/bounces": {
"delete": {
"description": "**This endpoint allows you to delete all emails on your bounces list.**\n\nThere are two options for deleting bounced emails: \n\n1. You can delete all bounced emails by setting `delete_all` to `true` in the request body. \n2. You can delete a selection of bounced emails by specifying the email addresses in the `emails` array of the request body.",
"operationId": "DELETE_suppression-bounces",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"delete_all": false,
"emails": [
"example@example.com",
"example2@example.com"
]
},
"properties": {
"delete_all": {
"description": "This parameter allows you to delete **every** email in your bounce list. This should not be used with the emails parameter.",
"type": "boolean"
},
"emails": {
"description": "Delete multiple emails from your bounce list at the same time. This should not be used with the delete_all parameter.",
"items": {
"type": "string"
},
"type": "array"
}
},
"type": "object"
}
}
}
},
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"nullable": true
}
}
},
"description": ""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete bounces",
"tags": [
"Bounces API"
],
"x-stoplight": {
"id": "DELETE_suppression-bounces",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve all of your bounces.**",
"operationId": "GET_suppression-bounces",
"parameters": [
{
"description": "Refers start of the time range in unix timestamp when a bounce was created (inclusive).",
"in": "query",
"name": "start_time",
"schema": {
"type": "integer"
}
},
{
"description": "Refers end of the time range in unix timestamp when a bounce was created (inclusive).",
"in": "query",
"name": "end_time",
"schema": {
"type": "integer"
}
},
{
"in": "header",
"name": "Accept",
"required": true,
"schema": {
"default": "application/json",
"type": "string"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"created": 1250337600,
"email": "example@example.com",
"reason": "550 5.1.1 The email account that you tried to reach does not exist. Please try double-checking the recipient's email address for typos or unnecessary spaces. Learn more at https://support.google.com/mail/answer/6596 o186si2389584ioe.63 - gsmtp ",
"status": "5.1.1"
},
{
"created": 1250337600,
"email": "example@example.com",
"reason": "550 5.1.1 : Recipient address rejected: User unknown in virtual alias table ",
"status": "5.1.1"
}
]
}
},
"schema": {
"items": {
"$ref": "#/components/schemas/bounce_response"
},
"type": "array"
}
}
},
"description": ""
},
"401": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve all bounces",
"tags": [
"Bounces API"
],
"x-stoplight": {
"id": "GET_suppression-bounces",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/suppression/bounces/{email}": {
"delete": {
"description": "**This endpoint allows you to remove an email address from your bounce list.**",
"operationId": "DELETE_suppression-bounces-email",
"parameters": [
{
"description": "The email address you would like to remove from the bounce list.",
"in": "query",
"name": "email_address",
"required": true,
"schema": {
"format": "email",
"type": "string"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"$ref": "#/components/requestBodies/DELETE_contactdb-lists-list_idBody"
},
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": ""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete a bounce",
"tags": [
"Bounces API"
],
"x-stoplight": {
"id": "DELETE_suppression-bounces-email",
"mock": {
"dynamic": false
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve a specific bounce by email address.**",
"operationId": "GET_suppression-bounces-email",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"created": 1443651125,
"email": "bounce1@test.com",
"reason": "550 5.1.1 The email account that you tried to reach does not exist. Please try double-checking the recipient's email address for typos or unnecessary spaces. Learn more at https://support.google.com/mail/answer/6596 o186si2389584ioe.63 - gsmtp ",
"status": "5.1.1"
}
]
}
},
"schema": {
"items": {
"$ref": "#/components/schemas/bounce_response"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve a Bounce",
"tags": [
"Bounces API"
],
"x-stoplight": {
"id": "GET_suppression-bounces-email",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"in": "path",
"name": "email",
"required": true,
"schema": {
"type": "string"
}
}
]
},
"/suppression/invalid_emails": {
"delete": {
"description": "**This endpoint allows you to remove email addresses from your invalid email address list.**\n\nThere are two options for deleting invalid email addresses: \n\n1) You can delete all invalid email addresses by setting `delete_all` to true in the request body.\n2) You can delete some invalid email addresses by specifying certain addresses in an array in the request body.",
"operationId": "DELETE_suppression-invalid_emails",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"delete_all": false,
"emails": [
"example1@example.com",
"example2@example.com"
]
},
"properties": {
"delete_all": {
"description": "Indicates if you want to remove all email address from the invalid emails list.",
"type": "boolean"
},
"emails": {
"description": "The list of specific email addresses that you want to remove.",
"items": {
"format": "email",
"type": "string"
},
"type": "array"
}
},
"type": "object"
}
}
}
},
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"properties": {},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete invalid emails",
"tags": [
"Invalid Emails API"
],
"x-stoplight": {
"id": "DELETE_suppression-invalidemails",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve a list of all invalid email addresses.**",
"operationId": "GET_suppression-invalid_emails",
"parameters": [
{
"description": "Refers start of the time range in unix timestamp when an invalid email was created (inclusive).",
"in": "query",
"name": "start_time",
"schema": {
"type": "integer"
}
},
{
"description": "Refers end of the time range in unix timestamp when an invalid email was created (inclusive).",
"in": "query",
"name": "end_time",
"schema": {
"type": "integer"
}
},
{
"description": "Limit the number of results to be displayed per page.",
"in": "query",
"name": "limit",
"schema": {
"type": "integer"
}
},
{
"description": "Paging offset. The point in the list to begin displaying results.",
"in": "query",
"name": "offset",
"schema": {
"type": "integer"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"created": 1449953655,
"email": "user1@example.com",
"reason": "Mail domain mentioned in email address is unknown"
},
{
"created": 1449939373,
"email": "user2@example.com",
"reason": "Mail domain mentioned in email address is unknown"
}
]
}
},
"schema": {
"description": "The list of invalid email addresses.",
"items": {
"$ref": "#/components/schemas/invalid-email"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve all invalid emails",
"tags": [
"Invalid Emails API"
],
"x-stoplight": {
"id": "GET_suppression-invalidemails",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/suppression/invalid_emails/{email}": {
"delete": {
"description": "**This endpoint allows you to remove a specific email address from the invalid email address list.**",
"operationId": "DELETE_suppression-invalid_emails-email",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"properties": {},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete a specific invalid email",
"tags": [
"Invalid Emails API"
],
"x-stoplight": {
"id": "DELETE_suppression-invalidemails-email",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve a specific invalid email addresses.**",
"operationId": "GET_suppression-invalid_emails-email",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"created": 1454433146,
"email": "test1@example.com",
"reason": "Mail domain mentioned in email address is unknown"
}
]
}
},
"schema": {
"description": "A specific invalid email.",
"items": {
"$ref": "#/components/schemas/invalid-email"
},
"maxItems": 1,
"minItems": 0,
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve a specific invalid email",
"tags": [
"Invalid Emails API"
],
"x-stoplight": {
"id": "GET_suppression-invalidemails-email",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"description": "The specific email address of the invalid email entry that you want to retrieve.",
"in": "path",
"name": "email",
"required": true,
"schema": {
"type": "string"
}
}
]
},
"/suppression/spam_reports": {
"delete": {
"description": "**This endpoint allows you to delete your spam reports.**\n\nDeleting a spam report will remove the suppression, meaning email will once again be sent to the previously suppressed address. This should be avoided unless a recipient indicates they wish to receive email from you again. You can use our [bypass filters](https://sendgrid.com/docs/ui/sending-email/index-suppressions/#bypass-suppressions) to deliver messages to otherwise suppressed addresses when exceptions are required.\n\nThere are two options for deleting spam reports: \n\n1. You can delete all spam reports by setting the `delete_all` field to `true` in the request body.\n2. You can delete a list of select spam reports by specifying the email addresses in the `emails` array of the request body.",
"operationId": "DELETE_suppression-spam_reports",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"delete_all": false,
"emails": [
"example1@example.com",
"example2@example.com"
]
},
"properties": {
"delete_all": {
"description": "Indicates if you want to delete all email addresses on the spam report list.",
"type": "boolean"
},
"emails": {
"description": "A list of specific email addresses that you want to remove from the spam report list.",
"items": {
"type": "string"
},
"type": "array"
}
},
"type": "object"
}
}
}
},
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"properties": {},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete spam reports",
"tags": [
"Spam Reports API"
],
"x-stoplight": {
"id": "DELETE_suppression-spamreports",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve all spam reports.**",
"operationId": "GET_suppression-spam_reports",
"parameters": [
{
"description": "The start of the time range when a spam report was created (inclusive). This is a unix timestamp.",
"in": "query",
"name": "start_time",
"schema": {
"type": "integer"
}
},
{
"description": "The end of the time range when a spam report was created (inclusive). This is a unix timestamp.",
"in": "query",
"name": "end_time",
"schema": {
"type": "integer"
}
},
{
"description": "Limit the number of results to be displayed per page.",
"in": "query",
"name": "limit",
"schema": {
"type": "integer"
}
},
{
"description": "Paging offset. The point in the list to begin displaying results.",
"in": "query",
"name": "offset",
"schema": {
"type": "integer"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"created": 1443651141,
"email": "user1@example.com",
"ip": "10.63.202.100"
},
{
"created": 1443651154,
"email": "user2@example.com",
"ip": "10.63.202.100"
}
]
}
},
"schema": {
"$ref": "#/components/schemas/spam-reports-response"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve all spam reports",
"tags": [
"Spam Reports API"
],
"x-stoplight": {
"id": "GET_suppression-spamreports",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/suppression/spam_reports/{email}": {
"delete": {
"description": "**This endpoint allows you to delete a specific spam report by email address.**\n\nDeleting a spam report will remove the suppression, meaning email will once again be sent to the previously suppressed address. This should be avoided unless a recipient indicates they wish to receive email from you again. You can use our [bypass filters](https://sendgrid.com/docs/ui/sending-email/index-suppressions/#bypass-suppressions) to deliver messages to otherwise suppressed addresses when exceptions are required.",
"operationId": "DELETE_suppression-spam_reports-email",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"properties": {},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete a specific spam report",
"tags": [
"Spam Reports API"
],
"x-stoplight": {
"id": "DELETE_suppression-spamreports-email",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve a specific spam report by email address.**",
"operationId": "GET_suppression-spam_reports-email",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"created": 1454433146,
"email": "test1@example.com",
"ip": "10.89.32.5"
}
]
}
},
"schema": {
"$ref": "#/components/schemas/spam-reports-response"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve a specific spam report",
"tags": [
"Spam Reports API"
],
"x-stoplight": {
"id": "GET_suppression-spamreports-email",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"description": "The email address of a specific spam report that you want to retrieve.",
"in": "path",
"name": "email",
"required": true,
"schema": {
"format": "email",
"type": "string"
}
}
]
},
"/suppression/unsubscribes": {
"get": {
"description": "**This endpoint allows you to retrieve a list of all email address that are globally suppressed.**",
"operationId": "GET_suppression-unsubscribes",
"parameters": [
{
"description": "Refers start of the time range in unix timestamp when an unsubscribe email was created (inclusive).",
"in": "query",
"name": "start_time",
"schema": {
"type": "integer"
}
},
{
"description": "Refers end of the time range in unix timestamp when an unsubscribe email was created (inclusive).",
"in": "query",
"name": "end_time",
"schema": {
"type": "integer"
}
},
{
"description": "The number of results to display on each page.",
"in": "query",
"name": "limit",
"schema": {
"type": "integer"
}
},
{
"description": "The point in the list of results to begin displaying global suppressions.",
"in": "query",
"name": "offset",
"schema": {
"type": "integer"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"created": 1443651141,
"email": "user1@example.com"
},
{
"created": 1443651154,
"email": "user2@example.com"
}
]
}
},
"schema": {
"items": {
"properties": {
"created": {
"description": "A Unix timestamp indicating when the recipient was added to the global suppression list.",
"type": "integer"
},
"email": {
"description": "The email address of the recipient who is globally suppressed.",
"format": "email",
"type": "string"
}
},
"required": [
"created",
"email"
],
"type": "object"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve all global suppressions",
"tags": [
"Suppressions - Global Suppressions"
],
"x-stoplight": {
"id": "GET_suppression-unsubscribes",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/teammates": {
"get": {
"description": "**This endpoint allows you to retrieve a list of all current Teammates.**\n\nYou can limit the number of results returned using the `limit` query paramater. To return results from a specific Teammate, use the `offset` paramter. The Response Headers will include pagination info.",
"operationId": "GET_v3-teammates",
"parameters": [
{
"description": "Number of items to return",
"in": "query",
"name": "limit",
"schema": {
"default": 500,
"maximum": 500,
"minimum": 0,
"type": "integer"
}
},
{
"description": "Paging offset",
"in": "query",
"name": "offset",
"schema": {
"default": 0,
"minimum": 0,
"type": "integer"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"results": [
{
"address": "123 Acme St",
"address2": "",
"city": "City",
"company": "ACME Inc.",
"country": "USA",
"email": "teammate1@example.com",
"first_name": "Jane",
"is_admin": true,
"last_name": "Doe",
"phone": "123-345-3453",
"state": "CA",
"user_type": "owner",
"username": "teammate1",
"website": "www.example.com",
"zip": "12345"
},
{
"address": "123 Acme St",
"address2": "",
"city": "City",
"company": "ACME Inc.",
"country": "USA",
"email": "teammate2@example.com",
"first_name": "John",
"is_admin": false,
"last_name": "Doe",
"phone": "123-345-3453",
"state": "CA",
"user_type": "teammate",
"username": "teammate2",
"website": "www.example.com",
"zip": "12345"
},
{
"address": "123 Acme St",
"address2": "",
"city": "City",
"company": "ACME Inc.",
"country": "USA",
"email": "teammate3@example.com",
"first_name": "Steve",
"is_admin": true,
"last_name": "Doe",
"phone": "123-345-3453",
"state": "CA",
"user_type": "admin",
"username": "teammate3",
"website": "www.example.com",
"zip": "12345"
}
]
}
}
},
"schema": {
"properties": {
"result": {
"items": {
"properties": {
"address": {
"description": "(optional) Teammate's address",
"type": "string"
},
"address2": {
"description": "(optional) Teammate's address",
"type": "string"
},
"city": {
"description": "(optional) Teammate's city",
"type": "string"
},
"country": {
"description": "(optional) Teammate's country",
"type": "string"
},
"email": {
"description": "Teammate's email",
"type": "string"
},
"first_name": {
"description": "Teammate's first name",
"type": "string"
},
"is_admin": {
"description": "Set to true if teammate has admin privileges",
"type": "boolean"
},
"last_name": {
"description": "Teammate's last name",
"type": "string"
},
"phone": {
"description": "(optional) Teammate's phone number",
"type": "string"
},
"state": {
"description": "(optional) Teammate's state",
"type": "string"
},
"user_type": {
"description": "Indicate the type of user: owner user, teammate admin user, or normal teammate",
"enum": [
"admin",
"owner",
"teammate"
],
"type": "string"
},
"username": {
"description": "Teammate's username",
"type": "string"
},
"website": {
"description": "(optional) Teammate's website",
"type": "string"
},
"zip": {
"description": "(optional) Teammate's zip",
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve all teammates",
"tags": [
"Teammates"
],
"x-stoplight": {
"id": "GET_v3-teammates",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"post": {
"description": "**This endpoint allows you to invite a Teammate to your account via email.**\n\nYou can set a Teammate's initial permissions using the `scopes` array in the request body. Teammate's will receive a minimum set of scopes from Twilio SendGrid that are necessary for the Teammate to function.\n\n**Note:** A teammate invite will expire after 7 days, but you may resend the invitation at any time to reset the expiration date.",
"operationId": "POST_v3-teammates",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"email": "teammate1@example.com",
"is_admin": false,
"scopes": [
"user.profile.read",
"user.profile.update"
]
},
"properties": {
"email": {
"description": "New teammate's email",
"maxLength": 255,
"minLength": 5,
"pattern": "^.*@.*\\..*",
"type": "string"
},
"is_admin": {
"default": false,
"description": "Set to true if teammate should be an admin user",
"type": "boolean"
},
"scopes": {
"description": "Set to specify list of scopes that teammate should have. Should be empty if teammate is an admin.",
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"email",
"scopes",
"is_admin"
],
"type": "object"
}
}
}
},
"responses": {
"201": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"email": "teammate1@example.com",
"is_admin": false,
"scopes": [
"user.profile.read",
"user.profile.update"
]
}
}
},
"schema": {
"properties": {
"email": {
"description": "Teammate's email address",
"type": "string"
},
"is_admin": {
"description": "Set to true if teammate should have admin privileges",
"type": "boolean"
},
"scopes": {
"description": "Initial set of permissions to give to teammate if they accept the invite",
"items": {},
"type": "array"
},
"token": {
"description": "Token to identify invite",
"type": "string"
}
},
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Invite teammate",
"tags": [
"Teammates"
],
"x-stoplight": {
"id": "POST_v3-teammates",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/teammates/pending": {
"get": {
"description": "**This endpoint allows you to retrieve a list of all pending Teammate invitations.**\n\nEach teammate invitation is valid for 7 days. Users may resend the invitation to refresh the expiration date.",
"operationId": "GET_v3-teammates-pending",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"result": [
{
"email": "user1@example.com",
"expiration_date": 1456424263,
"is_admin": false,
"pending_id": "abcd123abc",
"scopes": [
"user.profile.read",
"user.profile.edit"
]
},
{
"email": "user2@example.com",
"expiration_date": 1456424263,
"is_admin": true,
"pending_id": "bcde234bcd",
"scopes": []
}
]
}
}
},
"schema": {
"properties": {
"result": {
"items": {
"properties": {
"email": {
"description": "Email address teammate invite will be sent to",
"type": "string"
},
"expiration_date": {
"description": "timestamp indicates when invite will expire. Expiration is 7 days after invite creation",
"type": "integer"
},
"is_admin": {
"description": "Set to true to indicate teammate should have the same set of permissions as parent user",
"type": "boolean"
},
"scopes": {
"description": "List of permissions to give teammate if they accept",
"items": {
"type": "string"
},
"type": "array"
},
"token": {
"description": "Invitation token used to identify user",
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve all pending teammates",
"tags": [
"Teammates"
],
"x-stoplight": {
"id": "GET_v3-teammates-pending",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/teammates/pending/{token}": {
"delete": {
"description": "**This endpoint allows you to delete a pending teammate invite.**",
"operationId": "DELETE_v3-teammates-pending-token",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"204": {
"description": ""
},
"404": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete pending teammate",
"tags": [
"Teammates"
],
"x-stoplight": {
"id": "DELETE_v3-teammates-pending-token",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"description": "The token for the invite you want to delete.",
"in": "path",
"name": "token",
"required": true,
"schema": {
"type": "string"
}
}
]
},
"/teammates/pending/{token}/resend": {
"parameters": [
{
"description": "The token for the invite that you want to resend.",
"in": "path",
"name": "token",
"required": true,
"schema": {
"type": "string"
}
}
],
"post": {
"description": "**This endpoint allows you to resend a Teammate invitation.**\n\nTeammate invitations will expire after 7 days. Resending an invitation will reset the expiration date.",
"operationId": "POST_v3-teammates-pending-token-resend",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"email": "teammate1@example.com",
"is_admin": false,
"pending_id": "abc123abc",
"scopes": [
"user.profile.read",
"user.profile.update"
]
}
}
},
"schema": {
"properties": {
"email": {
"description": "Teammate's email address",
"type": "string"
},
"is_admin": {
"description": "Set to true if teammate should have admin privileges",
"type": "boolean"
},
"scopes": {
"description": "Initial set of permissions to give to teammate if they accept the invite",
"items": {
"type": "string"
},
"type": "array"
},
"token": {
"description": "ID to identify invite",
"type": "string"
}
},
"type": "object"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": "pending_key",
"message": "invalid pending key"
}
]
}
}
},
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Resend teammate invite",
"tags": [
"Teammates"
],
"x-stoplight": {
"id": "POST_v3-teammates-pending-token-resend",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/teammates/{username}": {
"delete": {
"description": "**This endpoint allows you to delete a teammate.**\n\n**Only the parent user or an admin teammate can delete another teammate.**",
"operationId": "DELETE_v3-teammates-username",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": "username",
"message": "username not found"
}
]
}
}
},
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete teammate",
"tags": [
"Teammates"
],
"x-stoplight": {
"id": "DELETE_v3-teammates-username",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve a specific Teammate by username.**\n\nYou can retrieve the username's for each of your Teammates using the \"Retrieve all Teammates\" endpoint.",
"operationId": "GET_v3-teammates-username",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"address": "123 Acme St",
"address2": "",
"city": "City",
"company": "ACME Inc.",
"country": "USA",
"email": "teammate1@example.com",
"first_name": "Jane",
"is_admin": true,
"last_name": "Doe",
"phone": "123-345-3453",
"scopes": [
"user.profile.read",
"user.profile.update",
"..."
],
"state": "CA",
"user_type": "admin",
"username": "teammate1",
"website": "www.example.com",
"zip": "12345"
}
}
},
"schema": {
"properties": {
"address": {
"description": "(optional) Teammate's address",
"type": "string"
},
"address2": {
"description": "(optional) Teammate's address",
"type": "string"
},
"city": {
"description": "(optional) Teammate's city",
"type": "string"
},
"country": {
"description": "(optional) Teammate's country",
"type": "string"
},
"email": {
"description": "Teammate's email",
"type": "string"
},
"first_name": {
"description": "Teammate's first name",
"type": "string"
},
"is_admin": {
"description": "Set to true if teammate has admin privileges",
"type": "boolean"
},
"last_name": {
"description": "Teammate's last name",
"type": "string"
},
"phone": {
"description": "(optional) Teammate's phone number",
"type": "string"
},
"scopes": {
"description": "Scopes associated to teammate",
"items": {},
"type": "array"
},
"state": {
"description": "(optional) Teammate's state",
"type": "string"
},
"user_type": {
"description": "Indicate the type of user: account owner, teammate admin user, or normal teammate",
"enum": [
"admin",
"owner",
"teammate"
],
"type": "string"
},
"username": {
"description": "Teammate's username",
"type": "string"
},
"website": {
"description": "(optional) Teammate's website",
"type": "string"
},
"zip": {
"description": "(optional) Teammate's zip",
"type": "string"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve specific teammate",
"tags": [
"Teammates"
],
"x-stoplight": {
"id": "GET_v3-teammates-username",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"description": "The username of the teammate that you want to retrieve.",
"in": "path",
"name": "username",
"required": true,
"schema": {
"type": "string"
}
}
],
"patch": {
"description": "**This endpoint allows you to update a teammate’s permissions.**\n\nTo turn a teammate into an admin, the request body should contain an `is_admin` set to `true`. Otherwise, set `is_admin` to `false` and pass in all the scopes that a teammate should have.\n\n**Only the parent user or other admin teammates can update another teammate’s permissions.**\n\n**Admin users can only update permissions.**",
"operationId": "PATCH_v3-teammates-username",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"is_admin": false,
"scopes": [
"user.profile.read",
"user.profile.edit"
]
},
"properties": {
"is_admin": {
"description": "Set to True if this teammate should be promoted to an admin user. If True, scopes should be an empty array.",
"type": "boolean"
},
"scopes": {
"description": "Provide list of scopes that should be given to teammate. If specifying list of scopes, is_admin should be set to False.",
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"scopes",
"is_admin"
],
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"address": "123 Acme St",
"address2": "",
"city": "City",
"company": "ACME Inc.",
"country": "USA",
"email": "teammate1@example.com",
"first_name": "Jane",
"is_admin": false,
"last_name": "Doe",
"phone": "123-345-3453",
"scopes": [
"user.profile.read",
"user.profile.edit"
],
"state": "CA",
"user_type": "teammate",
"username": "teammate1",
"website": "www.example.com",
"zip": "12345"
}
}
},
"schema": {
"properties": {
"address": {
"description": "(optional) Teammate's address",
"type": "string"
},
"address2": {
"description": "(optional) Teammate's address",
"type": "string"
},
"city": {
"description": "(optional) Teammate's city",
"type": "string"
},
"country": {
"description": "(optional) Teammate's country",
"type": "string"
},
"email": {
"description": "Teammate's email address",
"type": "string"
},
"first_name": {
"description": "Teammate's first name",
"type": "string"
},
"is_admin": {
"description": "Set to true if teammate has admin priveleges",
"type": "boolean"
},
"last_name": {
"description": "Teammate's last name",
"type": "string"
},
"phone": {
"description": "(optional) Teammate's phone number",
"type": "string"
},
"scopes": {
"description": "Scopes given to teammate",
"items": {
"type": "string"
},
"type": "array"
},
"state": {
"description": "(optional) Teammate's state",
"type": "string"
},
"user_type": {
"description": "Indicate the type of user: owner user, teammate admin user, or normal teammate",
"enum": [
"admin",
"owner",
"teammate"
],
"type": "string"
},
"username": {
"description": "Teammate's username",
"type": "string"
},
"website": {
"description": "(optional) Teammate's website",
"type": "string"
},
"zip": {
"description": "(optional) Teammate's zip",
"type": "string"
}
},
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": "scopes",
"message": "one or more of given scopes are invalid"
}
]
}
}
},
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": "username",
"message": "username not found"
}
]
}
}
},
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update teammate's permissions",
"tags": [
"Teammates"
],
"x-stoplight": {
"id": "PATCH_v3-teammates-username",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/templates": {
"get": {
"description": "**This endpoint allows you to retrieve all transactional templates.**",
"operationId": "GET_templates",
"parameters": [
{
"description": "Comma-delimited list specifying which generations of templates to return. Options are `legacy`, `dynamic` or `legacy,dynamic`.",
"in": "query",
"name": "generations",
"required": false,
"schema": {
"default": "legacy",
"enum": [
"legacy",
"dynamic",
"legacy,dynamic"
],
"type": "string"
}
},
{
"description": "The number of templates to be returned in each page of results",
"in": "query",
"name": "page_size",
"required": true,
"schema": {
"maximum": 200,
"minimum": 1,
"type": "number"
}
},
{
"description": "A token corresponding to a specific page of results, as provided by metadata",
"in": "query",
"name": "page_token",
"required": false,
"schema": {
"type": "string"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"_metadata": {
"count": 1,
"self": "https://api.sendgrid.com/v3/templates"
},
"result": [
{
"generation": "legacy",
"id": "fae7c985-eb92-4b47-9987-28ec29dbc698",
"name": "example_name",
"updated_at ": "2020-11-12 12:00:09",
"versions": []
}
]
}
}
},
"schema": {
"properties": {
"_metadata": {
"$ref": "#/components/schemas/_metadata"
},
"result": {
"description": "",
"items": {
"$ref": "#/components/schemas/transactional-templates-template-lean"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"": {
"type": "string"
},
"error_id": {
"type": "string"
},
"message": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve paged transactional templates.",
"tags": [
"Transactional Templates"
],
"x-stoplight": {
"id": "GET_templates",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"post": {
"description": "**This endpoint allows you to create a transactional template.**",
"operationId": "POST_templates",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"generation": "dynamic",
"name": "example_name"
},
"properties": {
"generation": {
"default": "legacy",
"description": "Defines whether the template supports dynamic replacement.",
"enum": [
"legacy",
"dynamic"
],
"type": "string"
},
"name": {
"description": "The name for the new transactional template.",
"maxLength": 100,
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
}
}
}
},
"responses": {
"201": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"generation": "legacy",
"id": "733ba07f-ead1-41fc-933a-3976baa23716",
"name": "example_name",
"updated_at ": "2021-04-28 13:12:46",
"versions": []
}
}
},
"schema": {
"$ref": "#/components/schemas/transactional_template"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Create a transactional template.",
"tags": [
"Transactional Templates"
],
"x-stoplight": {
"id": "POST_templates",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/templates/{template_id}": {
"delete": {
"description": "**This endpoint allows you to delete a transactional template.**",
"operationId": "DELETE_templates-template_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete a template.",
"tags": [
"Transactional Templates"
],
"x-stoplight": {
"id": "DELETE_templates-templateid",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve a single transactional template.**",
"operationId": "GET_templates-template_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"generation": "legacy",
"id": "40da60e6-66f3-4223-9406-ba58b7f55a62",
"name": "Duis in dolor",
"updated_at ": "2020-12-12 58:26:65",
"versions": []
}
}
},
"schema": {
"$ref": "#/components/schemas/transactional_template"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve a single transactional template.",
"tags": [
"Transactional Templates"
],
"x-stoplight": {
"id": "GET_templates-templateid",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"in": "path",
"name": "template_id",
"required": true,
"schema": {
"type": "string"
}
}
],
"patch": {
"description": "**This endpoint allows you to edit the name of a transactional template.**\n\nTo edit the template itself, [create a new transactional template version](https://sendgrid.api-docs.io/v3.0/transactional-templates-versions/create-a-new-transactional-template-version).",
"operationId": "PATCH_templates-template_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"name": "new_example_name"
},
"properties": {
"name": {
"description": "The name of the transactional template.",
"maxLength": 100,
"type": "string"
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"generation": "legacy",
"id": "733ba07f-ead1-41fc-933a-3976baa23716",
"name": "new_example_name",
"updated_at ": "2021-04-28 13:12:46",
"versions": []
}
}
},
"schema": {
"$ref": "#/components/schemas/transactional_template"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Edit a transactional template.",
"tags": [
"Transactional Templates"
],
"x-stoplight": {
"id": "PATCH_templates-templateid",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"post": {
"description": "**This endpoint allows you to duplicate a transactional template.**",
"operationId": "POST_templates-template_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"name": "example_name"
},
"properties": {
"name": {
"description": "The name for the new transactional template.",
"maxLength": 100,
"type": "string"
}
},
"type": "object"
}
}
}
},
"responses": {
"201": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"generation": "dynamic",
"id": "733ba07f-ead1-41fc-933a-3976baa23716",
"name": "example_name",
"updated_at ": "2020-12-12 58:26:65",
"versions": []
}
}
},
"schema": {
"$ref": "#/components/schemas/transactional_template"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Duplicate a transactional template.",
"tags": [
"Transactional Templates"
],
"x-stoplight": {
"id": "POST_templates-templateid",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/templates/{template_id}/versions": {
"parameters": [
{
"in": "path",
"name": "template_id",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"post": {
"description": "**This endpoint allows you to create a new version of a template.**",
"operationId": "POST_templates-template_id-versions",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"$ref": "#/components/requestBodies/transactional_template_version_create"
},
"responses": {
"201": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"active": 1,
"editor": "code",
"generate_plain_content": true,
"html_content": "<%body%>",
"id": "8aefe0ee-f12b-4575-b5b7-c97e21cb36f3",
"name": "example_version_name",
"plain_content": "<%body%>",
"subject": "<%subject%>",
"template_id": "ddb96bbc-9b92-425e-8979-99464621b543",
"updated_at": "2019-03-13 18:56:33"
}
}
},
"schema": {
"$ref": "#/components/schemas/transactional_template_version_output"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Create a new transactional template version.",
"tags": [
"Transactional Templates Versions"
],
"x-stoplight": {
"id": "POST_templates-templateid-versions",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/templates/{template_id}/versions/{version_id}": {
"delete": {
"description": "**This endpoint allows you to delete a transactional template version.**",
"operationId": "DELETE_templates-template_id-versions-version_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"204": {
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete a transactional template version.",
"tags": [
"Transactional Templates Versions"
],
"x-stoplight": {
"id": "DELETE_templates-templateid-versions-versionid",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve a specific version of a template.**",
"operationId": "GET_templates-template_id-versions-version_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"active": 1,
"editor": "code",
"generate_plain_content": true,
"html_content": "<%body%>",
"id": "8aefe0ee-f12b-4575-b5b7-c97e21cb36f3",
"name": "example_version_name",
"plain_content": "<%body%>",
"subject": "<%subject%>",
"template_id": "ddb96bbc-9b92-425e-8979-99464621b543",
"updated_at": "2019-03-13 18:56:33"
}
}
},
"schema": {
"$ref": "#/components/schemas/transactional_template_version_output"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve a specific transactional template version.",
"tags": [
"Transactional Templates Versions"
],
"x-stoplight": {
"id": "GET_templates-templateid-versions-versionid",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"description": " The ID of the original template",
"in": "path",
"name": "template_id",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"description": "The ID of the template version",
"in": "path",
"name": "version_id",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"patch": {
"description": "**This endpoint allows you to edit the content of your template version.**",
"operationId": "PATCH_templates-template_id-versions-version_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"$ref": "#/components/requestBodies/transactional_template_version_create"
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"active": 1,
"editor": "code",
"generate_plain_content": true,
"html_content": "<%body%>",
"id": "8aefe0ee-f12b-4575-b5b7-c97e21cb36f3",
"name": "example_version_name",
"plain_content": "<%body%>",
"subject": "<%subject%>",
"template_id": "ddb96bbc-9b92-425e-8979-99464621b543",
"updated_at": "2019-03-13 18:56:33"
}
}
},
"schema": {
"$ref": "#/components/schemas/transactional_template_version_output"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Edit a transactional template version.",
"tags": [
"Transactional Templates Versions"
],
"x-stoplight": {
"id": "PATCH_templates-templateid-versions-versionid",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/templates/{template_id}/versions/{version_id}/activate": {
"parameters": [
{
"description": "The ID of the original template",
"in": "path",
"name": "template_id",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"description": "The ID of the template version",
"in": "path",
"name": "version_id",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"post": {
"description": "**This endpoint allows you to activate a version of one of your templates.**",
"operationId": "POST_templates-template_id-versions-version_id-activate",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"active": 1,
"editor": "code",
"generate_plain_content": true,
"html_content": "<%body%>",
"id": "8aefe0ee-f12b-4575-b5b7-c97e21cb36f3",
"name": "example_version_name",
"plain_content": "<%body%>",
"subject": "<%subject%>",
"template_id": "ddb96bbc-9b92-425e-8979-99464621b543",
"updated_at": "2019-03-13 18:56:33"
}
}
},
"schema": {
"$ref": "#/components/schemas/transactional_template_version_output"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Activate a transactional template version.",
"tags": [
"Transactional Templates Versions"
],
"x-stoplight": {
"id": "POST_templates-templateid-versions-versionid-activate",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/tracking_settings": {
"get": {
"description": "**This endpoint allows you to retrieve a list of all tracking settings on your account.**",
"operationId": "GET_tracking_settings",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"result": [
{
"description": "lorem ipsum... .",
"enabled": true,
"name": "open",
"title": "Open Tracking"
}
]
}
}
},
"schema": {
"properties": {
"result": {
"description": "The list of all tracking settings.",
"items": {
"properties": {
"description": {
"description": "A description about the event that is being tracked.",
"type": "string"
},
"enabled": {
"description": "Indicates if this tracking setting is currently enabled.",
"type": "boolean"
},
"name": {
"description": "The name of the event being tracked.",
"type": "string"
},
"title": {
"description": "The title of the tracking setting.",
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve Tracking Settings",
"tags": [
"Settings - Tracking"
],
"x-stoplight": {
"id": "GET_trackingsettings",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/tracking_settings/click": {
"get": {
"description": "**This endpoint allows you to retrieve your current click tracking setting.**\n\nClick Tracking overrides all the links and URLs in your emails and points them to either SendGrid’s servers or the domain with which you branded your link. When a customer clicks a link, SendGrid tracks those [clicks](https://sendgrid.com/docs/glossary/clicks/).\n\nClick tracking helps you understand how users are engaging with your communications. SendGrid can track up to 1000 links per email",
"operationId": "GET_tracking_settings-click",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"enable_text": false,
"enabled": true
}
}
},
"schema": {
"$ref": "#/components/schemas/click-tracking"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve Click Track Settings",
"tags": [
"Settings - Tracking"
],
"x-stoplight": {
"id": "GET_trackingsettings-click",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"patch": {
"description": "**This endpoint allows you to enable or disable your current click tracking setting.**\n\nClick Tracking overrides all the links and URLs in your emails and points them to either SendGrid’s servers or the domain with which you branded your link. When a customer clicks a link, SendGrid tracks those [clicks](https://sendgrid.com/docs/glossary/clicks/).\n\nClick tracking helps you understand how users are engaging with your communications. SendGrid can track up to 1000 links per email",
"operationId": "PATCH_tracking_settings-click",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"enabled": true
},
"properties": {
"enabled": {
"description": "The setting you want to use for click tracking.",
"type": "boolean"
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"enable_text": false,
"enabled": true
}
}
},
"schema": {
"$ref": "#/components/schemas/click-tracking"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update Click Tracking Settings",
"tags": [
"Settings - Tracking"
],
"x-stoplight": {
"id": "PATCH_trackingsettings-click",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/tracking_settings/google_analytics": {
"get": {
"description": "**This endpoint allows you to retrieve your current setting for Google Analytics.**\n\n\nGoogle Analytics helps you understand how users got to your site and what they're doing there. For more information about using Google Analytics, please refer to [Google’s URL Builder](https://support.google.com/analytics/answer/1033867?hl=en) and their article on [\"Best Practices for Campaign Building\"](https://support.google.com/analytics/answer/1037445).\n\nWe default the settings to Google’s recommendations. For more information, see [Google Analytics Demystified](https://sendgrid.com/docs/ui/analytics-and-reporting/google-analytics/).",
"operationId": "GET_tracking_settings-google_analytics",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"enabled": true,
"utm_campaign": "",
"utm_content": "lotsandlotsofcontent",
"utm_medium": "",
"utm_source": "",
"utm_term": ""
}
}
},
"schema": {
"$ref": "#/components/schemas/google_analytics_settings"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve Google Analytics Settings",
"tags": [
"Settings - Tracking"
],
"x-stoplight": {
"id": "GET_trackingsettings-googleanalytics",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"patch": {
"description": "**This endpoint allows you to update your current setting for Google Analytics.**\n\nGoogle Analytics helps you understand how users got to your site and what they're doing there. For more information about using Google Analytics, please refer to [Google’s URL Builder](https://support.google.com/analytics/answer/1033867?hl=en) and their article on [\"Best Practices for Campaign Building\"](https://support.google.com/analytics/answer/1037445).\n\nWe default the settings to Google’s recommendations. For more information, see [Google Analytics Demystified](https://sendgrid.com/docs/ui/analytics-and-reporting/google-analytics/).",
"operationId": "PATCH_tracking_settings-google_analytics",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/google_analytics_settings"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"enabled": true,
"utm_campaign": "",
"utm_content": "lotsandlotsofcontent",
"utm_medium": "",
"utm_source": "",
"utm_term": ""
}
}
},
"schema": {
"$ref": "#/components/schemas/google_analytics_settings"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update Google Analytics Settings",
"tags": [
"Settings - Tracking"
],
"x-stoplight": {
"id": "PATCH_trackingsettings-googleanalytics",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/tracking_settings/open": {
"get": {
"description": "**This endpoint allows you to retrieve your current settings for open tracking.**\n\nOpen Tracking adds an invisible image at the end of the email which can track email opens.\n\nIf the email recipient has images enabled on their email client, a request to SendGrid’s server for the invisible image is executed and an open event is logged.\n\nThese events are logged in the Statistics portal, Email Activity interface, and are reported by the Event Webhook.",
"operationId": "GET_tracking_settings-open",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"enabled": true
}
}
},
"schema": {
"properties": {
"enabled": {
"description": "Indicates if open tracking is enabled.",
"type": "boolean"
}
},
"required": [
"enabled"
],
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Get Open Tracking Settings",
"tags": [
"Settings - Tracking"
],
"x-stoplight": {
"id": "GET_trackingsettings-open",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"patch": {
"description": "**This endpoint allows you to update your current settings for open tracking.**\n\nOpen Tracking adds an invisible image at the end of the email which can track email opens.\n\nIf the email recipient has images enabled on their email client, a request to SendGrid’s server for the invisible image is executed and an open event is logged.\n\nThese events are logged in the Statistics portal, Email Activity interface, and are reported by the Event Webhook.",
"operationId": "PATCH_tracking_settings-open",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"enabled": true
},
"properties": {
"enabled": {
"description": "The new status that you want to set for open tracking.",
"type": "boolean"
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"enabled": true
}
}
},
"schema": {
"properties": {
"enabled": {
"description": "Indicates if open tracking is enabled.",
"type": "boolean"
}
},
"required": [
"enabled"
],
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update Open Tracking Settings",
"tags": [
"Settings - Tracking"
],
"x-stoplight": {
"id": "PATCH_trackingsettings-open",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/tracking_settings/subscription": {
"get": {
"description": "**This endpoint allows you to retrieve your current settings for subscription tracking.**\n\nSubscription tracking adds links to the bottom of your emails that allows your recipients to subscribe to, or unsubscribe from, your emails.",
"operationId": "GET_tracking_settings-subscription",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"enabled": true,
"html_content": "Something something unsubscribe <% %> something something
\n",
"landing": "subscribehere
\n",
"plain_content": "Something something unsubscribe <% %> something something",
"replace": "thetag",
"url": "http://mydomain.com/parse"
}
}
},
"schema": {
"$ref": "#/components/schemas/subscription_tracking_settings"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve Subscription Tracking Settings",
"tags": [
"Settings - Tracking"
],
"x-stoplight": {
"id": "GET_trackingsettings-subscription",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"patch": {
"description": "**This endpoint allows you to update your current settings for subscription tracking.**\n\nSubscription tracking adds links to the bottom of your emails that allows your recipients to subscribe to, or unsubscribe from, your emails.",
"operationId": "PATCH_tracking_settings-subscription",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/subscription_tracking_settings"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"enabled": true,
"html_content": "html content",
"landing": "landing page html",
"plain_content": "text content",
"replace": "replacement tag",
"url": "http://mydomain.com/parse"
}
}
},
"schema": {
"$ref": "#/components/schemas/subscription_tracking_settings"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update Subscription Tracking Settings",
"tags": [
"Settings - Tracking"
],
"x-stoplight": {
"id": "PATCH_trackingsettings-subscription",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/user/account": {
"get": {
"description": "**This endpoint allows you to retrieve your user account details.**\n\nYour user's account information includes the user's account type and reputation.",
"operationId": "GET_user-account",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"reputation": 100,
"type": "paid"
}
}
},
"schema": {
"properties": {
"reputation": {
"description": "The sender reputation for this user.",
"type": "number"
},
"type": {
"description": "The type of account for this user.",
"enum": [
"free",
"paid"
],
"type": "string"
}
},
"required": [
"type",
"reputation"
],
"title": "GET User Account response",
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Get a user's account information.",
"tags": [
"Users API"
],
"x-stoplight": {
"id": "GET_user-account",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/user/credits": {
"get": {
"description": "**This endpoint allows you to retrieve the current credit balance for your account.**\n\nEach account has a credit balance, which is a base number of emails it can send before receiving per-email charges. For more information about credits and billing, see [Billing and Plan details information](https://sendgrid.com/docs/ui/account-and-settings/billing/).",
"operationId": "GET_user-credits",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"last_reset": "2013-01-01",
"next_reset": "2013-02-01",
"overage": 0,
"remain": 200,
"reset_frequency": "monthly",
"total": 200,
"used": 0
}
}
},
"schema": {
"properties": {
"last_reset": {
"description": "The date that your credit balance was last reset.",
"type": "string"
},
"next_reset": {
"description": "The next date that your credit balance will be reset.",
"type": "string"
},
"overage": {
"description": "The number of overdrawn credits for your account.",
"type": "integer"
},
"remain": {
"description": "The remaining number of credits available on your account.",
"type": "integer"
},
"reset_frequency": {
"description": "The frequency at which your credit balance will be reset.",
"type": "string"
},
"total": {
"description": "The total number of credits assigned to your account.",
"type": "integer"
},
"used": {
"description": "The number of credits that you have used.",
"type": "integer"
}
},
"required": [
"remain",
"total",
"overage",
"used",
"last_reset",
"next_reset",
"reset_frequency"
],
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve your credit balance",
"tags": [
"Users API"
],
"x-stoplight": {
"id": "GET_user-credits",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/user/email": {
"get": {
"description": "**This endpoint allows you to retrieve the email address currently on file for your account.**",
"operationId": "GET_user-email",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"email": "test@example.com"
}
}
},
"schema": {
"properties": {
"email": {
"description": "The email address currently on file for your account.",
"format": "email",
"type": "string"
}
},
"required": [
"email"
],
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve your account email address",
"tags": [
"Users API"
],
"x-stoplight": {
"id": "GET_user-email",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"put": {
"description": "**This endpoint allows you to update the email address currently on file for your account.**",
"operationId": "PUT_user-email",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"email": "example@example.com"
},
"properties": {
"email": {
"description": "The new email address that you would like to use for your account.",
"type": "string"
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"email": "example@example.com"
}
}
},
"schema": {
"properties": {
"email": {
"description": "The current email address on file for your account.",
"format": "email",
"type": "string"
}
},
"required": [
"email"
],
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update your account email address",
"tags": [
"Users API"
],
"x-stoplight": {
"id": "PUT_user-email",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/user/password": {
"put": {
"description": "**This endpoint allows you to update your password.**",
"operationId": "PUT_user-password",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"new_password": "new_password",
"old_password": "old_password"
},
"properties": {
"new_password": {
"description": "The new password you would like to use for your account.",
"type": "string"
},
"old_password": {
"description": "The old password for your account.",
"type": "string"
}
},
"required": [
"new_password",
"old_password"
],
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"properties": {},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update your password",
"tags": [
"Users API"
],
"x-stoplight": {
"id": "PUT_user-password",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/user/profile": {
"get": {
"operationId": "GET_user-profile",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"address": "814 West Chapman Avenue",
"address2": "",
"city": "Orange",
"company": "SendGrid",
"country": "US",
"first_name": "Test",
"last_name": "User",
"phone": "555-555-5555",
"state": "CA",
"website": "http://www.sendgrid.com",
"zip": "92868"
}
}
},
"schema": {
"properties": {
"address": {
"description": "The user's address.",
"type": "string"
},
"address2": {
"description": "The second line of the user's address.",
"type": "string"
},
"city": {
"description": "The user's city.",
"type": "string"
},
"company": {
"description": "The name of the user's company.",
"type": "string"
},
"country": {
"description": "The user's country.",
"type": "string"
},
"first_name": {
"description": "The user's first name.",
"type": "string"
},
"last_name": {
"description": "The user's last name.",
"type": "string"
},
"phone": {
"description": "The user's phone number.",
"type": "string"
},
"state": {
"description": "The user's state.",
"type": "string"
},
"website": {
"description": "The user's website URL.",
"type": "string"
},
"zip": {
"description": "The user's zip code.",
"type": "string"
}
},
"required": [
"address",
"city",
"company",
"country",
"first_name",
"last_name",
"phone",
"state",
"website",
"zip"
],
"title": "GET User Profile response",
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Get a user's profile",
"tags": [
"Users API"
],
"x-stoplight": {
"id": "GET_user-profile",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"patch": {
"description": "**This endpoint allows you to update your current profile details.**\n\nAny one or more of the parameters can be updated via the PATCH `/user/profile` endpoint. You must include at least one when you PATCH.",
"operationId": "PATCH_user-profile",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/user_profile"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"address": "814 West Chapman Avenue",
"address2": "",
"city": "Orange",
"company": "SendGrid",
"country": "US",
"first_name": "Example",
"last_name": "User",
"phone": "555-555-5555",
"state": "CA",
"website": "http://www.sendgrid.com",
"zip": "92868"
}
}
},
"schema": {
"$ref": "#/components/schemas/user_profile"
}
}
},
"description": ""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": null,
"message": "authorization required"
}
]
}
}
},
"schema": {
"$ref": "#/components/schemas/global_error_response_schema"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update a user's profile",
"tags": [
"Users API"
],
"x-stoplight": {
"id": "PATCH_user-profile",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/user/scheduled_sends": {
"get": {
"description": "**This endpoint allows you to retrieve all cancelled and paused scheduled send information.**\n\nThis endpoint will return only the scheduled sends that are associated with a `batch_id`. If you have scheduled a send using the `/mail/send` endpoint and the `send_at` field but no `batch_id`, the send will be scheduled for delivery; however, it will not be returned by this endpoint. For this reason, you should assign a `batch_id` to any scheduled send you may need to pause or cancel in the future.",
"operationId": "GET_user-scheduled_sends",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"batch_id": "QzZmYzLTVWIwYgYzJlM2NhNWI",
"status": "cancel"
},
{
"batch_id": "mQzZmYzLTVlM2NhNWIwYgYzJl",
"status": "cancel"
}
]
}
},
"schema": {
"items": {
"$ref": "#/components/schemas/user_scheduled_send_status"
},
"type": "array"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_cancelScheduledSendsErrors_400"
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve all scheduled sends",
"tags": [
"Cancel Scheduled Sends"
],
"x-stoplight": {
"id": "GET_user-scheduledsends",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"post": {
"description": "**This endpoint allows you to cancel or pause a scheduled send associated with a `batch_id`.**\n\nPassing this endpoint a `batch_id` and status will cancel or pause the scheduled send.\n\nOnce a scheduled send is set to `pause` or `cancel` you must use the \"Update a scheduled send\" endpoint to change its status or the \"Delete a cancellation or pause from a scheduled send\" endpoint to remove the status. Passing a status change to a scheduled send that has already been paused or cancelled will result in a `400` level status code.\n\nIf the maximum number of cancellations/pauses are added to a send, a `400` level status code will be returned.",
"operationId": "POST_user-scheduled_sends",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"batch_id": "YOUR_BATCH_ID",
"status": "pause"
},
"properties": {
"batch_id": {
"description": "The batch ID is the identifier that your scheduled mail sends share.",
"pattern": "^[a-zA-Z0-9]",
"type": "string"
},
"status": {
"default": "pause",
"description": "The status of the send you would like to implement. This can be pause or cancel. To delete a pause or cancel status see DELETE /v3/user/scheduled_sends/{batch_id}",
"enum": [
"pause",
"cancel"
],
"type": "string"
}
},
"required": [
"batch_id",
"status"
],
"title": "Cancel or pause a scheduled send request",
"type": "object"
}
}
}
},
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/user_scheduled_send_status"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_cancelScheduledSendsErrors_400"
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Cancel or pause a scheduled send",
"tags": [
"Cancel Scheduled Sends"
],
"x-stoplight": {
"id": "POST_user-scheduledsends",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/user/scheduled_sends/{batch_id}": {
"delete": {
"description": "**This endpoint allows you to delete the cancellation/pause of a scheduled send.**\n\nScheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled.",
"operationId": "DELETE_user-scheduled_sends-batch_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"204": {
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_cancelScheduledSendsErrors_400"
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete a cancellation or pause from a scheduled send",
"tags": [
"Cancel Scheduled Sends"
],
"x-stoplight": {
"id": "DELETE_user-scheduledsends-batchid",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve the cancel/paused scheduled send information for a specific `batch_id`.**",
"operationId": "GET_user-scheduled_sends-batch_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"batch_id": "HkJ5yLYULb7Rj8GKSx7u025ouWVlMgAi",
"status": "cancel"
},
{
"batch_id": "IbLdyLYULb7Rj8GKSx7u025ouWVlAiMg",
"status": "pause"
}
]
}
},
"schema": {
"items": {
"$ref": "#/components/schemas/user_scheduled_send_status"
},
"title": "Retrieve scheduled send response",
"type": "array"
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_cancelScheduledSendsErrors_400"
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve scheduled send",
"tags": [
"Cancel Scheduled Sends"
],
"x-stoplight": {
"id": "GET_user-scheduledsends-batchid",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"in": "path",
"name": "batch_id",
"required": true,
"schema": {
"type": "string"
}
}
],
"patch": {
"description": "**This endpoint allows you to update the status of a scheduled send for the given `batch_id`.**\n\nIf you have already set a `cancel` or `pause` status on a scheduled send using the \"Cancel or pause a scheduled send\" endpoint, you can update it's status using this endpoint. Attempting to update a status once it has been set with the \"Cancel or pause a scheduled send\" endpoint will result in a `400` error.",
"operationId": "PATCH_user-scheduled_sends-batch_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"status": "pause"
},
"properties": {
"status": {
"description": "The status you would like the scheduled send to have.",
"enum": [
"cancel",
"pause"
],
"type": "string"
}
},
"required": [
"status"
],
"type": "object"
}
}
}
},
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"nullable": true
}
}
},
"description": ""
},
"400": {
"$ref": "#/components/responses/trait_cancelScheduledSendsErrors_400"
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update a scheduled send",
"tags": [
"Cancel Scheduled Sends"
],
"x-stoplight": {
"id": "PATCH_user-scheduledsends-batchid",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/user/settings/enforced_tls": {
"get": {
"description": "**This endpoint allows you to retrieve your current Enforced TLS settings.**\n\nThe Enforced TLS settings specify whether or not the recipient is required to support TLS or have a valid certificate.\n\nIf either `require_tls` or `require_valid_cert` is set to `true`, the recipient must support TLS 1.1 or higher or have a valid certificate. If these conditions are not met, Twilio SendGrid will drop the message and send a block event with “TLS required but not supported” as the description.",
"operationId": "GET_user-settings-enforced_tls",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"require_tls": false,
"require_valid_cert": false
}
}
},
"schema": {
"$ref": "#/components/schemas/enforced-tls-request-response"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve current Enforced TLS settings.",
"tags": [
"Settings - Enforced TLS"
],
"x-stoplight": {
"id": "GET_user-settings-enforcedtls",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"patch": {
"description": "**This endpoint allows you to update your Enforced TLS settings.**\n\nTo require TLS from recipients, set `require_tls` to `true`. If either `require_tls` or `require_valid_cert` is set to `true`, the recipient must support TLS 1.1 or higher or have a valid certificate. If these conditions are not met, Twilio SendGrid will drop the message and send a block event with “TLS required but not supported” as the description.\n\n> Twilio SendGrid supports TLS 1.1 and higher and does not support older versions of TLS due to security vulnerabilities.",
"operationId": "PATCH_user-settings-enforced_tls",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/enforced-tls-request-response"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"require_tls": true,
"require_valid_cert": false
}
}
},
"schema": {
"$ref": "#/components/schemas/enforced-tls-request-response"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update Enforced TLS settings",
"tags": [
"Settings - Enforced TLS"
],
"x-stoplight": {
"id": "PATCH_user-settings-enforcedtls",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/user/username": {
"get": {
"description": "**This endpoint allows you to retrieve your current account username.**",
"operationId": "GET_user-username",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"user_id": 1,
"username": "test_username"
}
}
},
"schema": {
"properties": {
"user_id": {
"description": "The user ID for your account.",
"type": "integer"
},
"username": {
"description": "Your account username.",
"type": "string"
}
},
"required": [
"username",
"user_id"
],
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve your username",
"tags": [
"Users API"
],
"x-stoplight": {
"id": "GET_user-username",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"put": {
"description": "**This endpoint allows you to update the username for your account.**",
"operationId": "PUT_user-username",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"username": "test_username"
},
"properties": {
"username": {
"description": "The new username you would like to use for your account.",
"type": "string"
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"username": "test_username"
}
}
},
"schema": {
"properties": {
"username": {
"description": "The current username on file for your account.",
"type": "string"
}
},
"required": [
"username"
],
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update your username",
"tags": [
"Users API"
],
"x-stoplight": {
"id": "PUT_user-username",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/user/webhooks/event/settings": {
"get": {
"description": "**This endpoint allows you to retrieve your current event webhook settings.**\n\nIf an event type is marked as `true`, then the event webhook will include information about that event.\n\nSendGrid’s Event Webhook will notify a URL of your choice via HTTP POST with information about events that occur as SendGrid processes your email.\n\nCommon uses of this data are to remove unsubscribes, react to spam reports, determine unengaged recipients, identify bounced email addresses, or create advanced analytics of your email program.",
"operationId": "GET_user-webhooks-event-settings",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"bounce": false,
"click": true,
"deferred": false,
"delivered": false,
"dropped": true,
"enabled": false,
"group_resubscribe": false,
"group_unsubscribe": false,
"oauth_client_id": "est fugiat",
"oauth_token_url": "Duis in laborum sunt",
"open": true,
"processed": false,
"spam_report": false,
"unsubscribe": true,
"url": "incididunt reprehenderit"
}
}
},
"schema": {
"$ref": "#/components/schemas/event-webhook-response"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve Event Webhook settings",
"tags": [
"Webhooks"
],
"x-stoplight": {
"id": "GET_user-webhooks-event-settings",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"patch": {
"description": "**This endpoint allows you to update your current event webhook settings.**\n\nIf an event type is marked as `true`, then the event webhook will include information about that event.\n\nSendGrid’s Event Webhook will notify a URL of your choice via HTTP POST with information about events that occur as SendGrid processes your email.\n\nCommon uses of this data are to remove unsubscribes, react to spam reports, determine unengaged recipients, identify bounced email addresses, or create advanced analytics of your email program.",
"operationId": "PATCH_user-webhooks-event-settings",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/event-webhook-update-oauth-request"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"bounce": true,
"click": false,
"deferred": true,
"delivered": true,
"dropped": true,
"enabled": true,
"group_resubscribe": false,
"group_unsubscribe": true,
"oauth_client_id": "anim sunt",
"oauth_token_url": "ex",
"open": true,
"processed": true,
"spam_report": true,
"unsubscribe": true,
"url": "mollit laborum"
}
}
},
"schema": {
"$ref": "#/components/schemas/event-webhook-response"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update Event Notification Settings",
"tags": [
"Webhooks"
],
"x-stoplight": {
"id": "PATCH_user-webhooks-event-settings",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/user/webhooks/event/settings/signed": {
"get": {
"description": "**This endpoint allows you to retrieve your signed webhook's public key.**\n\nOnce you have enabled signing of the Event Webhook, you will need the public key provided to verify the signatures on requests coming from Twilio SendGrid. You can retrieve the public key from this endpoint at any time.\n\nFor more information about cryptographically signing the Event Webhook, see [Getting Started with the Event Webhook Security Features](https://sendgrid.com/docs/for-developers/tracking-events/getting-started-event-webhook-security-features).",
"operationId": "GET_user-webhooks-event-settings-signed",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"public_key": "anim quis in sint"
}
}
},
"schema": {
"properties": {
"public_key": {
"description": "The public key you can use to verify the Twilio SendGrid signature.",
"type": "string"
}
},
"required": [
"public_key"
],
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve Signed Webhook Public Key",
"tags": [
"Webhooks"
],
"x-stoplight": {
"id": "GET_user-webhooks-event-settings-signed",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"patch": {
"description": "**This endpoint allows you to enable or disable signing of the Event Webhook.**\n\nThis endpoint takes a single boolean request parameter, `enabled`. You may either enable or disable signing of the Event Webhook using this endpoint. Once enabled, you can retrieve your public key using the `/webhooks/event/settings/signed` endpoint.\n\nFor more information about cryptographically signing the Event Webhook, see [Getting Started with the Event Webhook Security Features](https://sendgrid.com/docs/for-developers/tracking-events/getting-started-event-webhook-security-features).",
"operationId": "PATCH_user-webhooks-event-settings-signed",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"enabled": true
},
"properties": {
"enabled": {
"type": "boolean"
}
},
"required": [
"enabled"
],
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"public_key": "voluptate id Excepteur proident"
}
}
},
"schema": {
"properties": {
"public_key": {
"description": "The public key you can use to verify the Twilio SendGrid signature.",
"type": "string"
}
},
"required": [
"public_key"
],
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": "anim Ut",
"message": "mollit consequat dolore commodo"
},
{
"message": "qui"
},
{
"message": "commodo dolor ipsum"
},
{
"field": "quis consectetur eiusmod ullamco laboris",
"message": "minim fugiat amet"
}
]
}
}
},
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"field": {
"nullable": true,
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"message"
],
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"401": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"field": "in proident",
"message": "fugiat"
},
{
"field": "ut",
"message": "adipisicing veniam laboris sunt ullamco"
},
{
"message": "id sunt consequat Duis irure"
},
{
"field": "in qui",
"message": "nisi"
},
{
"message": "tempor in eiusmod elit"
}
]
}
}
},
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"field": {
"nullable": true,
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"message"
],
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"500": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"message": "Excepteur culpa esse ea ut"
},
{
"message": "enim Excepteur dolore dolore"
},
{
"message": "dolor occaecat"
}
]
}
}
},
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"field": {
"nullable": true,
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"message"
],
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Enable/Disable Signed Webhook",
"tags": [
"Webhooks"
],
"x-stoplight": {
"id": "PATCH_user-webhooks-event-settings-signed",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/user/webhooks/event/test": {
"post": {
"description": "**This endpoint allows you to test your event webhook by sending a fake event notification post to the provided URL.**\n\nSendGrid’s Event Webhook will notify a URL of your choice via HTTP POST with information about events that occur as SendGrid processes your email.\n\nCommon uses of this data are to remove unsubscribes, react to spam reports, determine unengaged recipients, identify bounced email addresses, or create advanced analytics of your email program.\n\n>**Tip**: Retry logic for this endpoint differs from other endpoints, which use a rolling 24-hour retry.\n\nIf your web server does not return a 2xx response type, we will retry a POST request until we receive a 2xx response or the maximum time of 10 minutes has expired.",
"operationId": "POST_user-webhooks-event-test",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"oauth_client_id": "nisi",
"oauth_client_secret": "veniam commodo ex sunt",
"oauth_token_url": "dolor Duis",
"url": "mollit non ipsum magna"
},
"properties": {
"oauth_client_id": {
"description": "The client ID Twilio SendGrid sends to your OAuth server or service provider to generate an OAuth access token. When passing data in this field, you must also include the oauth_client_secret and oauth_token_url fields.",
"type": "string"
},
"oauth_client_secret": {
"description": "This secret is needed only once to create an access token. SendGrid will store this secret, allowing you to update your Client ID and Token URL without passing the secret to SendGrid again. When passing data in this field, you must also include the oauth_client_id and oauth_token_url fields.",
"type": "string"
},
"oauth_token_url": {
"description": "The URL where Twilio SendGrid sends the Client ID and Client Secret to generate an access token. This should be your OAuth server or service provider. When passing data in this field, you must also include the oauth_client_id and oauth_client_secret fields.",
"type": "string"
},
"url": {
"description": "The URL where you would like the test notification to be sent.",
"type": "string"
}
},
"type": "object"
}
}
}
},
"responses": {
"204": {
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Test Event Notification Settings",
"tags": [
"Webhooks"
],
"x-stoplight": {
"id": "POST_user-webhooks-event-test",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/user/webhooks/parse/settings": {
"get": {
"description": "**This endpoint allows you to retrieve all of your current inbound parse settings.**",
"operationId": "GET_user-webhooks-parse-settings",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"result": [
{
"hostname": "mail.mydomain.com",
"send_raw": true,
"spam_check": true,
"url": "http://mydomain.com/parse"
}
]
}
}
},
"schema": {
"properties": {
"result": {
"description": "The list of your current inbound parse settings.",
"items": {
"$ref": "#/components/schemas/parse-setting"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve all parse settings",
"tags": [
"Webhooks"
],
"x-stoplight": {
"id": "GET_user-webhooks-parse-settings",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"post": {
"description": "**This endpoint allows you to create a new inbound parse setting.**\n\nCreating an Inbound Parse setting requires two pieces of information: a `url` and a `hostname`.\n\nThe `hostname` must correspond to a domain authenticated by Twilio SendGrid on your account. If you need to complete domain authentication, you can use the [Twilio SendGrid App](https://app.sendgrid.com/settings/sender_auth) or the \"Authenticate a domain\" endpoint. See \"[How to Set Up Domain Authentication](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-domain-authentication/)\" for instructions.\n\nAny email received by the `hostname` will be parsed when you complete this setup. You must also add a Twilio SendGrid MX record to this domain's DNS records. See \"[Setting up the Inbound Parse Webhook](https://sendgrid.com/docs/for-developers/parsing-email/setting-up-the-inbound-parse-webhook/)\" for full instructions.\n\nThe `url` represents a location where the parsed message data will be delivered. Twilio SendGrid will make an HTTP POST request to this `url` with the message data. The `url` must be publicly reachable, and your application must return a `200` status code to signal that the message data has been received.",
"operationId": "POST_user-webhooks-parse-settings",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"$ref": "#/components/requestBodies/parse-setting"
},
"responses": {
"201": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"hostname": "myhostname.com",
"send_raw": true,
"spam_check": false,
"url": "http://email.myhostname.com"
}
}
},
"schema": {
"$ref": "#/components/schemas/parse-setting"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Create a parse setting",
"tags": [
"Settings - Inbound Parse"
],
"x-stoplight": {
"id": "POST_user-webhooks-parse-settings",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/user/webhooks/parse/settings/{hostname}": {
"delete": {
"description": "**This endpoint allows you to delete a specific inbound parse setting by hostname.**\n\nYou can retrieve all your Inbound Parse settings and their associated host names with the \"Retrieve all parse settings\" endpoint.",
"operationId": "DELETE_user-webhooks-parse-settings-hostname",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete a parse setting",
"tags": [
"Settings - Inbound Parse"
],
"x-stoplight": {
"id": "DELETE_user-webhooks-parse-settings-hostname",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve a specific inbound parse setting by hostname.**\n\nYou can retrieve all your Inbound Parse settings and their associated host names with the \"Retrieve all parse settings\" endpoint.",
"operationId": "GET_user-webhooks-parse-settings-hostname",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"hostname": "mail.mydomain.com",
"send_raw": true,
"spam_check": true,
"url": "http://mydomain.com/parse"
}
}
},
"schema": {
"$ref": "#/components/schemas/parse-setting"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve a specific parse setting",
"tags": [
"Settings - Inbound Parse"
],
"x-stoplight": {
"id": "GET_user-webhooks-parse-settings-hostname",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"description": "The hostname associated with the inbound parse setting that you would like to retrieve.",
"in": "path",
"name": "hostname",
"required": true,
"schema": {
"type": "string"
}
}
],
"patch": {
"description": "**This endpoint allows you to update a specific inbound parse setting by hostname.**\n\nYou can retrieve all your Inbound Parse settings and their associated host names with the \"Retrieve all parse settings\" endpoint.",
"operationId": "PATCH_user-webhooks-parse-settings-hostname",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"$ref": "#/components/requestBodies/parse-setting"
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"hostname": "mail.mydomain.com",
"send_raw": true,
"spam_check": true,
"url": "http://mydomain.com/parse"
}
}
},
"schema": {
"$ref": "#/components/schemas/parse-setting"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update a parse setting",
"tags": [
"Settings - Inbound Parse"
],
"x-stoplight": {
"id": "PATCH_user-webhooks-parse-settings-hostname",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/user/webhooks/parse/stats": {
"get": {
"description": "**This endpoint allows you to retrieve the statistics for your Parse Webhook useage.**\n\nSendGrid's Inbound Parse Webhook allows you to parse the contents and attachments of incomming emails. The Parse API can then POST the parsed emails to a URL that you specify. The Inbound Parse Webhook cannot parse messages greater than 30MB in size, including all attachments.\n\nThere are a number of pre-made integrations for the SendGrid Parse Webhook which make processing events easy. You can find these integrations in the [Library Index](https://sendgrid.com/docs/Integrate/libraries.html#-Webhook-Libraries).",
"operationId": "GET_user-webhooks-parse-stats",
"parameters": [
{
"description": "The number of statistics to return on each page.",
"in": "query",
"name": "limit",
"required": false,
"schema": {
"type": "string"
}
},
{
"description": "The number of statistics to skip.",
"in": "query",
"name": "offset",
"required": false,
"schema": {
"type": "string"
}
},
{
"description": "How you would like the statistics to by grouped. ",
"in": "query",
"name": "aggregated_by",
"required": false,
"schema": {
"enum": [
"day",
"week",
"month"
],
"type": "string"
}
},
{
"description": "The starting date of the statistics you want to retrieve. Must be in the format YYYY-MM-DD",
"in": "query",
"name": "start_date",
"required": true,
"schema": {
"type": "string"
}
},
{
"description": "The end date of the statistics you want to retrieve. Must be in the format YYYY-MM-DD",
"in": "query",
"name": "end_date",
"required": false,
"schema": {
"default": "The day the request is made.",
"type": "string"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"date": "2015-10-11",
"stats": [
{
"metrics": {
"received": 0
}
}
]
},
{
"date": "2015-10-12",
"stats": [
{
"metrics": {
"received": 0
}
}
]
},
{
"date": "2015-10-13",
"stats": [
{
"metrics": {
"received": 0
}
}
]
},
{
"date": "2015-10-14",
"stats": [
{
"metrics": {
"received": 0
}
}
]
},
{
"date": "2015-10-15",
"stats": [
{
"metrics": {
"received": 0
}
}
]
},
{
"date": "2015-10-16",
"stats": [
{
"metrics": {
"received": 0
}
}
]
},
{
"date": "2015-10-17",
"stats": [
{
"metrics": {
"received": 0
}
}
]
},
{
"date": "2015-10-18",
"stats": [
{
"metrics": {
"received": 0
}
}
]
},
{
"date": "2015-10-19",
"stats": [
{
"metrics": {
"received": 0
}
}
]
},
{
"date": "2015-10-20",
"stats": [
{
"metrics": {
"received": 0
}
}
]
},
{
"date": "2015-10-21",
"stats": [
{
"metrics": {
"received": 0
}
}
]
},
{
"date": "2015-10-22",
"stats": [
{
"metrics": {
"received": 0
}
}
]
},
{
"date": "2015-10-23",
"stats": [
{
"metrics": {
"received": 0
}
}
]
},
{
"date": "2015-10-24",
"stats": [
{
"metrics": {
"received": 0
}
}
]
},
{
"date": "2015-10-25",
"stats": [
{
"metrics": {
"received": 0
}
}
]
},
{
"date": "2015-10-26",
"stats": [
{
"metrics": {
"received": 0
}
}
]
},
{
"date": "2015-10-27",
"stats": [
{
"metrics": {
"received": 0
}
}
]
},
{
"date": "2015-10-28",
"stats": [
{
"metrics": {
"received": 0
}
}
]
},
{
"date": "2015-10-29",
"stats": [
{
"metrics": {
"received": 0
}
}
]
},
{
"date": "2015-10-30",
"stats": [
{
"metrics": {
"received": 0
}
}
]
},
{
"date": "2015-10-31",
"stats": [
{
"metrics": {
"received": 0
}
}
]
},
{
"date": "2015-11-01",
"stats": [
{
"metrics": {
"received": 0
}
}
]
},
{
"date": "2015-11-02",
"stats": [
{
"metrics": {
"received": 0
}
}
]
},
{
"date": "2015-11-03",
"stats": [
{
"metrics": {
"received": 0
}
}
]
},
{
"date": "2015-11-04",
"stats": [
{
"metrics": {
"received": 0
}
}
]
},
{
"date": "2015-11-05",
"stats": [
{
"metrics": {
"received": 0
}
}
]
},
{
"date": "2015-11-06",
"stats": [
{
"metrics": {
"received": 0
}
}
]
},
{
"date": "2015-11-07",
"stats": [
{
"metrics": {
"received": 0
}
}
]
},
{
"date": "2015-11-08",
"stats": [
{
"metrics": {
"received": 0
}
}
]
},
{
"date": "2015-11-09",
"stats": [
{
"metrics": {
"received": 0
}
}
]
},
{
"date": "2015-11-10",
"stats": [
{
"metrics": {
"received": 0
}
}
]
}
]
}
},
"schema": {
"items": {
"properties": {
"date": {
"description": "The date that the stats were collected.",
"type": "string"
},
"stats": {
"description": "The Parse Webhook usage statistics.",
"items": {
"properties": {
"metrics": {
"properties": {
"received": {
"description": "The number of emails received and parsed by the Parse Webhook.",
"type": "number"
}
},
"required": [
"received"
],
"type": "object"
}
},
"type": "object"
},
"type": "array"
}
},
"required": [
"date",
"stats"
],
"type": "object"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieves Inbound Parse Webhook statistics.",
"tags": [
"Webhooks"
],
"x-stoplight": {
"id": "GET_user-webhooks-parse-stats",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/validations/email": {
"post": {
"description": "**This endpoint allows you to validate an email address.**",
"operationId": "POST_validations-email",
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"email": "example@example.com",
"source": "signup"
},
"properties": {
"email": {
"description": "The email address that you want to validate.",
"type": "string"
},
"source": {
"description": "A one-word classifier for where this validation originated.",
"type": "string"
}
},
"required": [
"email"
],
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"result": {
"checks": {
"additional": {
"has_known_bounces": false,
"has_suspected_bounces": false
},
"domain": {
"has_mx_or_a_record": true,
"has_valid_address_syntax": true,
"is_suspected_disposable_address": false
},
"local_part": {
"is_suspected_role_address": false
}
},
"email": "cedric@fogowl.com",
"host": "fogowl.com",
"ip_address": "192.168.1.1",
"local": "cedric",
"score": 0.85021,
"verdict": "Valid"
}
}
}
},
"schema": {
"properties": {
"result": {
"properties": {
"checks": {
"description": "Granular checks for email address validity.",
"properties": {
"additional": {
"description": "Additional checks on the email address.",
"properties": {
"has_known_bounces": {
"description": "WHether email sent to this address from your account has bounced.",
"type": "boolean"
},
"has_suspected_bounces": {
"description": "Whether our model predicts that the email address might bounce.",
"type": "boolean"
}
},
"required": [
"has_known_bounces",
"has_suspected_bounces"
],
"type": "object"
},
"domain": {
"description": "Checks on the domain portion of the email address.",
"properties": {
"has_mx_or_a_record": {
"description": "Whether the email has appropriate DNS records to deliver a message. ",
"type": "boolean"
},
"has_valid_address_syntax": {
"description": "Whether the email address syntax is valid.",
"type": "boolean"
},
"is_suspected_disposable_address": {
"description": "Whether the domain appears to be from a disposable email address service.",
"type": "boolean"
}
},
"required": [
"has_valid_address_syntax",
"has_mx_or_a_record",
"is_suspected_disposable_address"
],
"type": "object"
},
"local_part": {
"description": "Checks on the local part of the email address.",
"properties": {
"is_suspected_role_address": {
"description": "Whether the local part of email appears to be a role or group (e.g., hr, admin)",
"type": "boolean"
}
},
"required": [
"is_suspected_role_address"
],
"type": "object"
}
},
"required": [
"domain",
"local_part",
"additional"
],
"type": "object"
},
"email": {
"description": "The email being validated",
"format": "email",
"type": "string"
},
"host": {
"description": "The domain of the email address.",
"format": "hostname",
"type": "string"
},
"ip_address": {
"description": "The IP address associated with this email.",
"type": "string"
},
"local": {
"description": "The local part of the email address.",
"type": "string"
},
"score": {
"description": "A numeric representation of the email validity.",
"type": "number"
},
"source": {
"description": "The source of the validation, as per the API request.",
"type": "string"
},
"suggestion": {
"description": "A suggested correction in the event of domain name typos (e.g., gmial.com)",
"type": "string"
},
"verdict": {
"description": "A generic classification of whether or not the email address is valid.",
"enum": [
"Valid",
"Risky",
"Invalid"
],
"type": "string"
}
},
"required": [
"email",
"verdict",
"score",
"local",
"host",
"checks",
"ip_address"
],
"type": "object"
}
},
"required": [
"result"
],
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Validate an email",
"tags": [
"Email Address Validation"
],
"x-stoplight": {
"id": "POST_validations-email",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/verified_senders": {
"get": {
"description": "**This endpoint allows you to retrieve all the Sender Identities associated with an account.**\n\nThis endpoint will return both verified and unverified senders.\n\nYou can limit the number of results returned using the `limit`, `lastSeenID`, and `id` query string parameters.\n\n* `limit` allows you to specify an exact number of Sender Identities to return.\n* `lastSeenID` will return senders with an ID number occuring after the passed in ID. In other words, the `lastSeenID` provides a starting point from which SendGrid will iterate to find Sender Identities associated with your account.\n* `id` will return information about only the Sender Identity passed in the request.",
"operationId": "GET_verified_senders",
"parameters": [
{
"in": "query",
"name": "limit",
"schema": {
"type": "number"
}
},
{
"in": "query",
"name": "lastSeenID",
"schema": {
"type": "number"
}
},
{
"in": "query",
"name": "id",
"schema": {
"type": "integer"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"results": [
{
"address": "1234 Fake St.",
"address2": "PO Box 1234",
"city": "San Francisco",
"country": "USA",
"from_email": "orders@example.com",
"from_name": "Example Orders",
"id": 1234,
"locked": false,
"nickname": "Example Orders",
"reply_to": "orders@example.com",
"reply_to_name": "Example Orders",
"state": "CA",
"verified": true,
"zip": "94105"
},
{
"address": "1234 Fake St.",
"address2": "PO Box 1234",
"city": "San Francisco",
"country": "USA",
"from_email": "support@example.com",
"from_name": "Example Support",
"id": 1235,
"locked": false,
"nickname": "Example Support",
"reply_to": "support@example.com",
"reply_to_name": "Example Support",
"state": "CA",
"verified": true,
"zip": "94105"
}
]
}
}
},
"schema": {
"properties": {
"results": {
"items": {
"$ref": "#/components/schemas/verified-sender-response-schema"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Get All Verified Senders",
"tags": [
"Sender Verification"
],
"x-stoplight": {
"id": "GET_verifiedsenders",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"post": {
"description": "**This endpoint allows you to create a new Sender Identify**.\n\nUpon successful submission of a `POST` request to this endpoint, an identity will be created, and a verification email will be sent to the address assigned to the `from_email` field. You must complete the verification process using the sent email to fully verify the sender.\n\nIf you need to resend the verification email, you can do so with the Resend Verified Sender Request, `/resend/{id}`, endpoint.\n\nIf you need to authenticate a domain rather than a Single Sender, see the [Domain Authentication API](https://sendgrid.api-docs.io/v3.0/domain-authentication/authenticate-a-domain).",
"operationId": "POST_verified_senders",
"requestBody": {
"$ref": "#/components/requestBodies/verified-sender-request-schema"
},
"responses": {
"201": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"address": "1234 Fake St.",
"address2": "PO Box 1234",
"city": "San Francisco",
"country": "USA",
"from_email": "orders@example.com",
"from_name": "Example Orders",
"id": 1234,
"locked": false,
"nickname": "Example Orders",
"reply_to": "orders@example.com",
"reply_to_name": "Example Orders",
"state": "CA",
"verified": true,
"zip": "94105"
}
}
},
"schema": {
"$ref": "#/components/schemas/verified-sender-response-schema"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"type": "string"
},
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"message",
"error_id"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"errors"
],
"type": "object"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Create Verified Sender Request",
"tags": [
"Sender Verification"
],
"x-stoplight": {
"id": "POST_verifiedsenders",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/verified_senders/domains": {
"get": {
"description": "**This endpoint returns a list of domains known to implement DMARC and categorizes them by failure type — hard failure or soft failure**.\n\nDomains listed as hard failures will not deliver mail when used as a [Sender Identity](https://sendgrid.com/docs/for-developers/sending-email/sender-identity/) due to the domain's DMARC policy settings.\n\nFor example, using a `yahoo.com` email address as a Sender Identity will likely result in the rejection of your mail. For more information about DMARC, see [Everything about DMARC](https://sendgrid.com/docs/ui/sending-email/dmarc/).",
"operationId": "GET_verified_senders-domains",
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"results": {
"hard_failures": [
"yahoo.com"
],
"soft_failures": [
"gmail.com"
]
}
}
}
},
"schema": {
"properties": {
"results": {
"properties": {
"hard_failures": {
"items": {
"type": "string"
},
"type": "array"
},
"soft_failures": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"soft_failures",
"hard_failures"
],
"type": "object"
}
},
"required": [
"results"
],
"type": "object"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Domain Warn List",
"tags": [
"Sender Verification"
],
"x-stoplight": {
"id": "GET_verifiedsenders-domains",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/verified_senders/resend/{id}": {
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"post": {
"description": "**This endpoint allows you to resend a verification email to a specified Sender Identity**.\n\nPassing the `id` assigned to a Sender Identity to this endpoint will resend a verification email to the `from_address` associated with the Sender Identity. This can be useful if someone loses their verification email or needs to have it resent for any other reason.\n\nYou can retrieve the IDs associated with Sender Identities by passing a \"Get All Verified Senders\" endpoint.",
"operationId": "POST_verified_senders-resend-id",
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"message",
"error_id"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"errors"
],
"type": "object"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"message",
"error_id"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"errors"
],
"type": "object"
}
}
},
"description": ""
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Resend Verified Sender Request",
"tags": [
"Sender Verification"
],
"x-stoplight": {
"id": "POST_verifiedsenders-resend-id",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/verified_senders/steps_completed": {
"get": {
"description": "**This endpoint allows you to determine which of SendGrid’s verification processes have been completed for an account**.\n\nThis endpoint returns boolean values, `true` and `false`, for [Domain Authentication](https://sendgrid.com/docs/for-developers/sending-email/sender-identity/#domain-authentication), `domain_verified`, and [Single Sender Verification](https://sendgrid.com/docs/for-developers/sending-email/sender-identity/#single-sender-verification), `sender_verified`, for the account.\n\nAn account may have one, both, or neither verification steps completed. If you need to authenticate a domain rather than a Single Sender, see the \"Authenticate a domain\" endpoint.",
"operationId": "GET_verified_senders-steps_completed",
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"results": {
"domain_verified": true,
"sender_verified": true
}
}
}
},
"schema": {
"properties": {
"results": {
"properties": {
"domain_verified": {
"type": "boolean"
},
"sender_verified": {
"type": "boolean"
}
},
"type": "object"
}
},
"type": "object"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"$ref": "#/components/responses/trait_globalErrors_404"
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Completed Steps",
"tags": [
"Sender Verification"
],
"x-stoplight": {
"id": "GET_verifiedsenders-stepscompleted",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/verified_senders/verify/{token}": {
"get": {
"description": "**This endpoint allows you to verify a sender requests.**\n\nThe token is generated by SendGrid and included in a verification email delivered to the address that's pending verification.",
"operationId": "GET_verified_senders-verify-token",
"responses": {
"204": {
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"$ref": "#/components/responses/trait_globalErrors_403"
},
"404": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"message",
"error_id"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"errors"
],
"type": "object"
}
}
},
"description": ""
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Verify Sender Request",
"tags": [
"Sender Verification"
],
"x-stoplight": {
"id": "GET_verifiedsenders-verify-token",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"in": "path",
"name": "token",
"required": true,
"schema": {
"type": "string"
}
}
]
},
"/verified_senders/{id}": {
"delete": {
"description": "**This endpoint allows you to delete a Sender Identity**.\n\nPass the `id` assigned to a Sender Identity to this endpoint to delete the Sender Identity from your account.\n\nYou can retrieve the IDs associated with Sender Identities using the \"Get All Verified Senders\" endpoint.",
"operationId": "DELETE_verified_senders-id",
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"message",
"error_id"
],
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"message",
"error_id"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"errors"
],
"type": "object"
}
}
},
"description": ""
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete Verified Sender",
"tags": [
"Sender Verification"
],
"x-stoplight": {
"id": "DELETE_verifiedsenders-id",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"patch": {
"description": "**This endpoint allows you to update an existing Sender Identity**.\n\nPass the `id` assigned to a Sender Identity to this endpoint as a path parameter. Include any fields you wish to update in the request body in JSON format.\n\nYou can retrieve the IDs associated with Sender Identities by passing a `GET` request to the Get All Verified Senders endpoint, `/verified_senders`.\n\n**Note:** Unlike a `PUT` request, `PATCH` allows you to update only the fields you wish to edit. Fields that are not passed as part of a request will remain unaltered.",
"operationId": "PATCH_verified_senders-id",
"requestBody": {
"$ref": "#/components/requestBodies/verified-sender-request-schema"
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"address": "1234 Fake St.",
"address2": "PO Box 1234",
"city": "San Francisco",
"country": "USA",
"from_email": "orders@example.com",
"from_name": "Example Orders",
"id": 1234,
"locked": false,
"nickname": "Example Orders",
"reply_to": "orders@example.com",
"reply_to_name": "Example Orders",
"state": "CA",
"verified": true,
"zip": "94105"
}
}
},
"schema": {
"$ref": "#/components/schemas/verified-sender-response-schema"
}
}
},
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"type": "string"
},
"field": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"message",
"error_id"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"errors"
],
"type": "object"
}
}
},
"description": ""
},
"401": {
"$ref": "#/components/responses/trait_globalErrors_401"
},
"403": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"message",
"error_id"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"errors"
],
"type": "object"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"error_id": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"message",
"error_id"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"errors"
],
"type": "object"
}
}
},
"description": ""
},
"500": {
"$ref": "#/components/responses/trait_globalErrors_500"
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Edit Verified Sender",
"tags": [
"Sender Verification"
],
"x-stoplight": {
"id": "PATCH_verifiedsenders-id",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/whitelabel/dns/email": {
"post": {
"description": "**This endpoint is used to share DNS records with a colleagues**\n\nUse this endpoint to send SendGrid-generated DNS record information to a co-worker so they can enter it into your DNS provider to validate your domain and link branding. \n\nWhat type of records are sent will depend on whether you have chosen Automated Security or not. When using Automated Security, SendGrid provides you with three CNAME records. If you turn Automated Security off, you are instead given TXT and MX records.\n\nIf you pass a `link_id` to this endpoint, the generated email will supply the DNS records necessary to complete [Link Branding](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-link-branding/) setup. If you pass a `domain_id` to this endpoint, the generated email will supply the DNS records needed to complete [Domain Authentication](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-domain-authentication/). Passing both IDs will generate an email with the records needed to complete both setup steps.\n\nYou can retrieve all your domain IDs from the returned `id` fields for each domain using the \"List all authenticated domains\" endpoint. You can retrieve all of your link IDs using the \"Retrieve all branded links\" endpoint.",
"operationId": "POST_whitelabel-dns-email",
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"domain_id": 46873408,
"email": "my_colleague@example.com",
"link_id": 29719392,
"message": "DNS Record for verification"
},
"properties": {
"domain_id": {
"description": "The ID of your SendGrid domain record.",
"minimum": 0,
"type": "integer"
},
"email": {
"description": "The email address to send the DNS information to.",
"format": "email",
"type": "string"
},
"link_id": {
"description": "The ID of the branded link.",
"minimum": 0,
"type": "integer"
},
"message": {
"default": "Please set these DNS records in our hosting solution.",
"description": "A custom text block to include in the email body sent with the records.",
"type": "string"
}
},
"required": [
"link_id",
"domain_id",
"email"
],
"type": "object"
}
}
}
},
"responses": {
"204": {
"description": ""
},
"400": {
"content": {
"application/json": {
"schema": {
"properties": {
"errors": {
"properties": {
"error": {
"type": "string"
},
"field": {
"type": "string"
}
},
"type": "object"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Email DNS records to a co-worker",
"tags": [
"Email CNAME records"
],
"x-stoplight": {
"id": "POST_whitelabel-dns-email",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/whitelabel/domains": {
"get": {
"description": "**This endpoint allows you to retrieve a list of all domains you have authenticated.**",
"operationId": "GET_whitelabel-domains",
"parameters": [
{
"description": "Number of domains to return.",
"in": "query",
"name": "limit",
"schema": {
"type": "integer"
}
},
{
"description": "Paging offset.",
"in": "query",
"name": "offset",
"schema": {
"type": "integer"
}
},
{
"description": "Exclude subuser domains from the result.",
"in": "query",
"name": "exclude_subusers",
"schema": {
"type": "boolean"
}
},
{
"description": "The username associated with an authenticated domain.",
"in": "query",
"name": "username",
"schema": {
"type": "string"
}
},
{
"description": "Search for authenticated domains.",
"in": "query",
"name": "domain",
"schema": {
"type": "string"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"automatic_security": true,
"custom_spf": true,
"default": true,
"dns": {
"dkim1": {
"data": "s1._domainkey.u7.wl.sendgrid.net",
"host": "s1._domainkey.example.com",
"type": "cname",
"valid": true
},
"dkim2": {
"data": "s2._domainkey.u7.wl.sendgrid.net",
"host": "s2._domainkey.example.com",
"type": "cname",
"valid": true
},
"mail_cname": {
"data": "u7.wl.sendgrid.net",
"host": "mail.example.com",
"type": "cname",
"valid": true
}
},
"domain": "example.com",
"id": 1,
"ips": [
"192.168.1.1",
"192.168.1.2"
],
"legacy": false,
"subdomain": "mail",
"user_id": 7,
"username": "jane@example.com",
"valid": true
},
{
"automatic_security": true,
"custom_spf": false,
"default": true,
"dns": {
"dkim1": {
"data": "k=rsa; t=s; p=publicKey",
"host": "example2.com",
"type": "txt",
"valid": false
},
"dkim2": {
"data": "k=rsa; t=s p=publicKey",
"host": "example2.com",
"type": "txt",
"valid": false
},
"mail_cname": {
"data": "sendgrid.net",
"host": "news.example2.com",
"type": "mx",
"valid": false
}
},
"domain": "example2.com",
"id": 2,
"ips": [],
"legacy": false,
"subdomain": "new",
"user_id": 8,
"username": "john@example2.com",
"valid": false
}
]
}
},
"schema": {
"$ref": "#/components/schemas/domain-authentication-200-response"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "List all authenticated domains",
"tags": [
"Domain Authentication"
],
"x-stoplight": {
"id": "GET_whitelabel-domains",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"post": {
"description": "**This endpoint allows you to authenticate a domain.**\n\nIf you are authenticating a domain for a subuser, you have two options:\n1. Use the \"username\" parameter. This allows you to authenticate a domain on behalf of your subuser. This means the subuser is able to see and modify the authenticated domain.\n2. Use the Association workflow (see Associate Domain section). This allows you to authenticate a domain created by the parent to a subuser. This means the subuser will default to the assigned domain, but will not be able to see or modify that authenticated domain. However, if the subuser authenticates their own domain it will overwrite the assigned domain.",
"operationId": "POST_whitelabel-domains",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"automatic_security": false,
"custom_spf": true,
"default": true,
"domain": "example.com",
"ips": [
"192.168.1.1",
"192.168.1.2"
],
"subdomain": "news",
"username": "john@example.com"
},
"properties": {
"automatic_security": {
"description": "Whether to allow SendGrid to manage your SPF records, DKIM keys, and DKIM key rotation.",
"type": "boolean"
},
"custom_dkim_selector": {
"description": "Add a custom DKIM selector. Accepts three letters or numbers.",
"type": "string"
},
"custom_spf": {
"description": "Specify whether to use a custom SPF or allow SendGrid to manage your SPF. This option is only available to authenticated domains set up for manual security.",
"type": "boolean"
},
"default": {
"description": "Whether to use this authenticated domain as the fallback if no authenticated domains match the sender's domain.",
"type": "boolean"
},
"domain": {
"description": "Domain being authenticated.",
"type": "string"
},
"ips": {
"description": "The IP addresses that will be included in the custom SPF record for this authenticated domain.",
"items": {
"type": "string"
},
"type": "array"
},
"subdomain": {
"description": "The subdomain to use for this authenticated domain.",
"type": "string"
},
"username": {
"description": "The username associated with this domain.",
"type": "string"
}
},
"required": [
"domain"
],
"type": "object"
}
}
}
},
"responses": {
"201": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"automatic_security": true,
"custom_spf": false,
"default": true,
"dns": {
"dkim1": {
"data": "s1.domainkey.u1446226.wl.sendgrid.net",
"host": "s1._domainkey.example.com",
"type": "cname",
"valid": false
},
"dkim2": {
"data": "s2.domainkey.u1446226.wl.sendgrid.net",
"host": "s2._domainkey.example.com",
"type": "cname",
"valid": false
},
"mail_cname": {
"data": "u1446226.wl.sendgrid.net",
"host": "example.example.com",
"type": "cname",
"valid": false
}
},
"domain": "example.com",
"id": 302183,
"ips": [],
"legacy": false,
"subdomain": "example",
"user_id": 1446226,
"username": "mbernier",
"valid": false
}
}
},
"schema": {
"$ref": "#/components/schemas/authentication_domain"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Authenticate a domain",
"tags": [
"Domain Authentication"
],
"x-stoplight": {
"id": "POST_whitelabel-domains",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/whitelabel/domains/default": {
"get": {
"description": "**This endpoint allows you to retrieve the default authentication for a domain.**\n\nWhen creating or updating a domain authentication, you can set the domain as a default. The default domain will be used to send all mail. If you have multiple authenticated domains, the authenticated domain matching the domain of the From address will be used, and the default will be overridden.\n\nThis endpoint will return a default domain and its details only if a default is set. You are not required to set a default. If you do not set a default domain, this endpoint will return general information about your domain authentication status.",
"operationId": "GET_whitelabel-domains-default",
"parameters": [
{
"description": "The domain to find a default authentication.",
"in": "query",
"name": "domain",
"schema": {
"type": "string"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"automatic_security": true,
"custom_spf": true,
"default": true,
"dns": {
"dkim1": {
"data": "s1._domainkey.u7.wl.sendgrid.net",
"host": "s1._domainkey.example.com",
"type": "cname",
"valid": true
},
"dkim2": {
"data": "s2._domainkey.u7.wl.sendgrid.net",
"host": "s2._domainkey.example.com",
"type": "cname",
"valid": true
},
"mail_cname": {
"data": "u7.wl.sendgrid.net",
"host": "mail.example.com",
"type": "cname",
"valid": true
}
},
"domain": "example.com",
"id": 1,
"ips": [
"192.168.1.1",
"192.168.1.2"
],
"legacy": false,
"subdomain": "mail",
"user_id": 7,
"username": "jane@example.com",
"valid": true
}
]
}
},
"schema": {
"$ref": "#/components/schemas/domain-authentication-200-response"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Get the default authentication",
"tags": [
"Domain Authentication"
],
"x-stoplight": {
"id": "GET_whitelabel-domains-default",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/whitelabel/domains/subuser": {
"delete": {
"description": "**This endpoint allows you to disassociate a specific authenticated domain from a subuser.**\n\nAuthenticated domains can be associated with (i.e. assigned to) subusers from a parent account. This functionality allows subusers to send mail using their parent's domain authentication. To associate an authenticated domain with a subuser, the parent account must first authenticate and validate the domain. The parent may then associate the authenticated domain via the subuser management tools.",
"operationId": "DELETE_whitelabel-domains-subuser",
"parameters": [
{
"description": "Username for the subuser to find associated authenticated domain.",
"in": "query",
"name": "username",
"schema": {
"type": "string"
}
}
],
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Disassociate an authenticated domain from a given user.",
"tags": [
"Domain Authentication"
],
"x-stoplight": {
"id": "DELETE_whitelabel-domains-subuser",
"mock": {
"dynamic": false
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve all of the authenticated domains that have been assigned to a specific subuser.**\n\nAuthenticated domains can be associated with (i.e. assigned to) subusers from a parent account. This functionality allows subusers to send mail using their parent's domain authentication. To associate an authenticated domain with a subuser, the parent account must first authenticate and validate the domain. The parent may then associate the authenticated domain via the subuser management tools.",
"operationId": "GET_whitelabel-domains-subuser",
"parameters": [
{
"description": "Username for the subuser to find associated authenticated domain.",
"in": "query",
"name": "username",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"automatic_security": false,
"custom_spf": true,
"default": false,
"dns": {
"dkim": {
"data": "k=rsa; t=s; p=publicKey",
"host": "s1._domainkey.example.com",
"type": "txt",
"valid": false
},
"domain_spf": {
"data": "v=spf1 include:mail.example.com -all",
"host": "example.com",
"type": "txt",
"valid": false
},
"mail_server": {
"data": "sendgrid.net",
"host": "mail.example.com",
"type": "mx",
"valid": false
},
"subdomain_spf": {
"data": "v=spf1 ip4:192.168.1.1 ip4:192.168.0.1 -all",
"host": "mail.example.com",
"type": "txt",
"valid": false
}
},
"domain": "example.com",
"id": 1,
"ips": [],
"legacy": false,
"subdomain": "mail",
"user_id": 7,
"username": "mail@example.com",
"valid": false
}
}
},
"schema": {
"$ref": "#/components/schemas/domain_authentication_domain_spf"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "List the authenticated domain associated with the given user.",
"tags": [
"Domain Authentication"
],
"x-stoplight": {
"id": "GET_whitelabel-domains-subuser",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/whitelabel/domains/{domain_id}": {
"delete": {
"description": "**This endpoint allows you to delete an authenticated domain.**",
"operationId": "DELETE_whitelabel-domains-domain_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete an authenticated domain.",
"tags": [
"Domain Authentication"
],
"x-stoplight": {
"id": "DELETE_whitelabel-domains-domainid",
"mock": {
"dynamic": false
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve a specific authenticated domain.**",
"operationId": "GET_whitelabel-domains-domain_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"automatic_security": true,
"custom_spf": false,
"default": true,
"dns": {
"dkim1": {
"data": "s1._domainkey.u7.wl.sendgrid.net",
"host": "s1._domainkey.example.com",
"type": "cname",
"valid": true
},
"dkim2": {
"data": "s2._domainkey.u7.wl.sendgrid.net",
"host": "s2._domainkey.example.com",
"type": "cname",
"valid": true
},
"mail_cname": {
"data": "u7.wl.sendgrid.net",
"host": "mail.example.com",
"type": "cname",
"valid": true
}
},
"domain": "example.com",
"id": 45373692,
"ips": [
"127.0.0.1"
],
"legacy": false,
"subdomain": "sub",
"user_id": 66036447,
"username": "jdoe",
"valid": true
}
}
},
"schema": {
"$ref": "#/components/schemas/authentication_domain"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve an authenticated domain",
"tags": [
"Domain Authentication"
],
"x-stoplight": {
"id": "GET_whitelabel-domains-domainid",
"mock": {
"dynamic": false
},
"public": true
}
},
"parameters": [
{
"in": "path",
"name": "domain_id",
"required": true,
"schema": {
"type": "string"
}
}
],
"patch": {
"description": "**This endpoint allows you to update the settings for an authenticated domain.**",
"operationId": "PATCH_whitelabel-domains-domain_id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"custom_spf": true,
"default": false
},
"properties": {
"custom_spf": {
"default": false,
"description": "Indicates whether to generate a custom SPF record for manual security.",
"type": "boolean"
},
"default": {
"default": false,
"description": "Indicates whether this is the default authenticated domain.",
"type": "boolean"
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"automatic_security": true,
"custom_spf": true,
"default": true,
"dns": {
"dkim1": {
"data": "s1._domainkey.u7.wl.sendgrid.net",
"host": "s1._domainkey.example.com",
"type": "cname",
"valid": true
},
"dkim2": {
"data": "s2._domainkey.u7.wl.sendgrid.net",
"host": "s2._domainkey.example.com",
"type": "cname",
"valid": true
},
"mail_cname": {
"data": "u7.wl.sendgrid.net",
"host": "mail.example.com",
"type": "cname",
"valid": true
}
},
"domain": "example.com",
"id": 1,
"ips": [
"192.168.1.1",
"192.168.1.2"
],
"legacy": false,
"subdomain": "mail",
"user_id": 7,
"username": "jane@example.com",
"valid": true
},
{
"automatic_security": true,
"custom_spf": false,
"default": true,
"dns": {
"dkim1": {
"data": "k=rsa; t=s; p=publicKey",
"host": "example2.com",
"type": "txt",
"valid": false
},
"dkim2": {
"data": "k=rsa; t=s p=publicKey",
"host": "example2.com",
"type": "txt",
"valid": false
},
"mail_cname": {
"data": "sendgrid.net",
"host": "news.example2.com",
"type": "mx",
"valid": false
}
},
"domain": "example2.com",
"id": 2,
"ips": [],
"legacy": false,
"subdomain": "new",
"user_id": 8,
"username": "john@example2.com",
"valid": false
}
]
}
},
"schema": {
"$ref": "#/components/schemas/domain-authentication-200-response"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update an authenticated domain",
"tags": [
"Domain Authentication"
],
"x-stoplight": {
"id": "PATCH_whitelabel-domains-domainid",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/whitelabel/domains/{domain_id}/subuser": {
"parameters": [
{
"description": "ID of the authenticated domain to associate with the subuser",
"in": "path",
"name": "domain_id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"post": {
"description": "**This endpoint allows you to associate a specific authenticated domain with a subuser.**\n\nAuthenticated domains can be associated with (i.e. assigned to) subusers from a parent account. This functionality allows subusers to send mail using their parent's domain authentication. To associate an authenticated domain with a subuser, the parent account must first authenticate and validate the domain. The parent may then associate the authenticated domain via the subuser management tools.",
"operationId": "POST_whitelabel-domains-domain_id-subuser",
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"username": "jdoe"
},
"properties": {
"username": {
"description": "Username to associate with the authenticated domain.",
"type": "string"
}
},
"required": [
"username"
],
"type": "object"
}
}
}
},
"responses": {
"201": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"automatic_security": false,
"custom_spf": true,
"default": false,
"dns": {
"dkim": {
"data": "k=rsa; t=s; p=publicKey",
"host": "s1._domainkey.example.com",
"type": "txt",
"valid": false
},
"domain_spf": {
"data": "v=spf1 include:mail.example.com -all",
"host": "example.com",
"type": "txt",
"valid": false
},
"mail_server": {
"data": "sendgrid.net",
"host": "mail.example.com",
"type": "mx",
"valid": false
},
"subdomain_spf": {
"data": "v=spf1 ip4:192.168.1.1 ip4:192.168.0.1 -all",
"host": "mail.example.com",
"type": "txt",
"valid": false
}
},
"domain": "example.com",
"id": 1,
"ips": [],
"legacy": false,
"subdomain": "mail",
"user_id": 7,
"username": "mail@example.com",
"valid": false
}
}
},
"schema": {
"$ref": "#/components/schemas/domain_authentication_domain_spf"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Associate an authenticated domain with a given user.",
"tags": [
"Domain Authentication"
],
"x-stoplight": {
"id": "POST_whitelabel-domains-domainid-subuser",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/whitelabel/domains/{id}/ips": {
"parameters": [
{
"description": "ID of the domain to which you are adding an IP",
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"post": {
"description": "**This endpoint allows you to add an IP address to an authenticated domain.**",
"operationId": "POST_whitelabel-domains-id-ips",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"ip": "192.168.0.1"
},
"properties": {
"ip": {
"description": "IP to associate with the domain. Used for manually specifying IPs for custom SPF.",
"type": "string"
}
},
"required": [
"ip"
],
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"automatic_security": false,
"custom_spf": true,
"default": false,
"dns": {
"dkim": {
"data": "k=rsa; t=s; p=publicKey",
"host": "s1._domainkey.example.com",
"type": "txt",
"valid": false
},
"domain_spf": {
"data": "v=spf1 include:mail.example.com -all",
"host": "example.com",
"type": "txt",
"valid": false
},
"mail_server": {
"data": "sendgrid.net",
"host": "mail.example.com",
"type": "mx",
"valid": false
},
"subdomain_spf": {
"data": "v=spf1 ip4:192.168.1.1 ip4:192.168.0.1 -all",
"host": "mail.example.com",
"type": "txt",
"valid": false
}
},
"domain": "example.com",
"id": 1,
"ips": [],
"legacy": false,
"subdomain": "mail",
"user_id": 7,
"username": "john@example.com",
"valid": false
}
}
},
"schema": {
"$ref": "#/components/schemas/domain_authentication_domain_spf"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Add an IP to an authenticated domain",
"tags": [
"Domain Authentication"
],
"x-stoplight": {
"id": "POST_whitelabel-domains-id-ips",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/whitelabel/domains/{id}/ips/{ip}": {
"delete": {
"description": "**This endpoint allows you to remove an IP address from that domain's authentication.**",
"operationId": "DELETE_whitelabel-domains-id-ips-ip",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"automatic_security": false,
"custom_spf": true,
"default": false,
"dns": {
"dkim": {
"data": "k=rsa; t=s; p=publicKey",
"host": "s1._domainkey.example.com",
"type": "txt",
"valid": false
},
"domain_spf": {
"data": "v=spf1 include:mail.example.com -all",
"host": "example.com",
"type": "txt",
"valid": false
},
"mail_server": {
"data": "sendgrid.net",
"host": "mail.example.com",
"type": "mx",
"valid": false
},
"subdomain_spf": {
"data": "v=spf1 ip4:192.168.1.1 ip4:192.168.0.1 -all",
"host": "mail.example.com",
"type": "txt",
"valid": false
}
},
"domain": "example.com",
"id": 1,
"ips": [],
"legacy": false,
"subdomain": "mail",
"user_id": 7,
"username": "mail@example.com",
"valid": false
}
}
},
"schema": {
"$ref": "#/components/schemas/domain_authentication_domain_spf"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Remove an IP from an authenticated domain.",
"tags": [
"Domain Authentication"
],
"x-stoplight": {
"id": "DELETE_whitelabel-domains-id-ips-ip",
"mock": {
"dynamic": false
},
"public": true
}
},
"parameters": [
{
"description": "ID of the domain to delete the IP from.",
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
},
{
"description": "IP to remove from the domain.",
"in": "path",
"name": "ip",
"required": true,
"schema": {
"type": "string"
}
}
]
},
"/whitelabel/domains/{id}/validate": {
"parameters": [
{
"description": "ID of the domain to validate.",
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"post": {
"description": "**This endpoint allows you to validate an authenticated domain. If it fails, it will return an error message describing why the domain could not be validated.**",
"operationId": "POST_whitelabel-domains-id-validate",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"id": 1,
"valid": true,
"validation_resuts": {
"dkim1": {
"reason": null,
"valid": true
},
"dkim2": {
"reason": null,
"valid": true
},
"mail_cname": {
"reason": "Expected your MX record to be \"mx.sendgrid.net\" but found \"example.com\".",
"valid": false
},
"spf": {
"reason": null,
"valid": true
}
}
}
}
},
"schema": {
"properties": {
"id": {
"description": "The ID of the authenticated domain.",
"type": "integer"
},
"valid": {
"description": "Indicates if this is a valid authenticated domain.",
"type": "boolean"
},
"validation_results": {
"description": "The individual DNS records that are checked when validating, including the reason for any invalid DNS records.",
"properties": {
"dkim1": {
"description": "A DNS record for this authenticated domain.",
"properties": {
"reason": {
"nullable": true,
"type": "string"
},
"valid": {
"description": "Indicates if the DNS record is valid.",
"type": "boolean"
}
},
"type": "object"
},
"dkim2": {
"description": "A DNS record for this authenticated domain.",
"properties": {
"reason": {
"nullable": true,
"type": "string"
},
"valid": {
"description": "Indicates if the DNS record is valid.",
"type": "boolean"
}
},
"type": "object"
},
"mail_cname": {
"description": "The CNAME record for the authenticated domain.",
"properties": {
"reason": {
"description": "The reason this record is invalid.",
"nullable": true,
"type": "string"
},
"valid": {
"description": "Indicates if this DNS record is valid.",
"type": "boolean"
}
},
"type": "object"
},
"spf": {
"description": "The SPF record for the authenticated domain.",
"properties": {
"reason": {
"nullable": true,
"type": "string"
},
"valid": {
"description": "Indicates if the SPF record is valid.",
"type": "boolean"
}
},
"type": "object"
}
},
"type": "object"
}
},
"type": "object"
}
}
},
"description": ""
},
"500": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"message": "internal error getting TXT"
}
]
}
}
},
"schema": {
"properties": {
"errors": {
"items": {
"properties": {
"message": {
"description": "A message explaining the reason for the error.",
"type": "string"
}
},
"required": [
"message"
],
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Validate a domain authentication.",
"tags": [
"Domain Authentication"
],
"x-stoplight": {
"id": "POST_whitelabel-domains-id-validate",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/whitelabel/ips": {
"get": {
"description": "**This endpoint allows you to retrieve all of the Reverse DNS records created by this account.**\n\nYou may include a search key by using the `ip` query string parameter. This enables you to perform a prefix search for a given IP segment (e.g., `?ip=\"192.\"`).\n\nUse the `limit` query string parameter to reduce the number of records returned. All records will be returned if you have fewer records than the specified limit.\n\nThe `offset` query string parameter allows you to specify a non-zero index from which records will be returned. For example, if you have ten records, `?offset=5` will return the last five records (at indexes 5 through 9). The list starts at index zero.",
"operationId": "GET_whitelabel-ips",
"parameters": [
{
"description": "The maximum number of results to retrieve.",
"in": "query",
"name": "limit",
"schema": {
"type": "integer"
}
},
{
"description": "The point in the list of results to begin retrieving IP addresses from.",
"in": "query",
"name": "offset",
"schema": {
"type": "integer"
}
},
{
"description": "The IP address segment that you'd like to use in a prefix search.",
"in": "query",
"name": "ip",
"schema": {
"type": "string"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"a_record": {
"data": "192.168.1.1",
"host": "o1.email.example.com",
"type": "a",
"valid": true
},
"domain": "example.com",
"id": 1,
"ip": "192.168.1.1",
"legacy": false,
"rdns": "o1.email.example.com",
"subdomain": "email",
"users": [
{
"user_id": 7,
"username": "john@example.com"
},
{
"user_id": 8,
"username": "jane@example.com"
}
],
"valid": true
},
{
"a_record": {
"data": "192.168.1.2",
"host": "o2.email.example.com",
"type": "a",
"valid": true
},
"domain": "example.com",
"id": 2,
"ip": "192.168.1.2",
"legacy": false,
"rdns": "o2.email.example.com",
"subdomain": "email",
"users": [
{
"user_id": 7,
"username": "john@example.com"
},
{
"user_id": 9,
"username": "jane@example2.com"
}
],
"valid": true
}
]
}
},
"schema": {
"items": {
"$ref": "#/components/schemas/reverse_dns"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve all reverse DNS records",
"tags": [
"Reverse DNS"
],
"x-stoplight": {
"id": "GET_whitelabel-ips",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"post": {
"description": "**This endpoint allows you to set up reverse DNS.**",
"operationId": "POST_whitelabel-ips",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"domain": "example.com",
"ip": "192.168.1.1",
"subdomain": "email"
},
"properties": {
"domain": {
"description": "The root, or sending, domain that will be used to send message from the IP address.",
"type": "string"
},
"ip": {
"description": "The IP address for which you want to set up reverse DNS.",
"type": "string"
},
"subdomain": {
"description": "The subdomain that will be used to send emails from the IP address. This should be the same as the subdomain used to set up an authenticated domain.",
"type": "string"
}
},
"required": [
"ip",
"domain"
],
"type": "object"
}
}
}
},
"responses": {
"201": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"a_record": {
"data": "192.168.1.2",
"host": "o1.email.example.com",
"type": "a",
"valid": true
},
"domain": "example.com",
"id": 123,
"ip": "192.168.1.2",
"legacy": false,
"rdns": "o1.email.example.com",
"subdomain": "email",
"users": [],
"valid": true
}
}
},
"schema": {
"$ref": "#/components/schemas/reverse_dns"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Set up reverse DNS",
"tags": [
"Reverse DNS"
],
"x-stoplight": {
"id": "POST_whitelabel-ips",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/whitelabel/ips/{id}": {
"delete": {
"description": "**This endpoint allows you to delete a reverse DNS record.**\n\nA call to this endpoint will respond with a 204 status code if the deletion was successful.\n\nYou can retrieve the IDs associated with all your reverse DNS records using the \"Retrieve all reverse DNS records\" endpoint.",
"operationId": "DELETE_whitelabel-ips-id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete a reverse DNS record",
"tags": [
"Reverse DNS"
],
"x-stoplight": {
"id": "DELETE_whitelabel-ips-id",
"mock": {
"dynamic": false
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve a reverse DNS record.**\n\nYou can retrieve the IDs associated with all your reverse DNS records using the \"Retrieve all reverse DNS records\" endpoint.",
"operationId": "GET_whitelabel-ips-id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"a_record": {
"data": "192.168.1.1",
"host": "o1.email.example.com",
"type": "a",
"valid": true
},
"domain": "example.com",
"id": 123,
"ip": "192.168.1.1",
"legacy": false,
"rdns": "o1.email.example.com",
"subdomain": "email",
"users": [
{
"user_id": 7,
"username": "john@example.com"
}
],
"valid": true
}
}
},
"schema": {
"$ref": "#/components/schemas/reverse_dns"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve a reverse DNS record",
"tags": [
"Reverse DNS"
],
"x-stoplight": {
"id": "GET_whitelabel-ips-id",
"mock": {
"dynamic": false
},
"public": true
}
},
"parameters": [
{
"description": "The ID of the reverse DNS record that you would like to retrieve.",
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
]
},
"/whitelabel/ips/{id}/validate": {
"parameters": [
{
"description": "The ID of the reverse DNS record that you would like to validate.",
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"post": {
"description": "**This endpoint allows you to validate a reverse DNS record.**\n\nAlways check the `valid` property of the response’s `validation_results.a_record` object. This field will indicate whether it was possible to validate the reverse DNS record. If the `validation_results.a_record.valid` is `false`, this indicates only that Twilio SendGrid could not determine the validity your reverse DNS record — it may still be valid.\n\nIf validity couldn’t be determined, you can check the value of `validation_results.a_record.reason` to find out why.\n\nYou can retrieve the IDs associated with all your reverse DNS records using the \"Retrieve all reverse DNS records\" endpoint.",
"operationId": "POST_whitelabel-ips-id-validate",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"id": 123456,
"valid": false,
"validation_results": {
"a_record": {
"reason": "Failed to resolve A Record at o1.ptr4283.example.com: lookup o1.ptr4283.example.com on 192.168.0.10:53: no such host",
"valid": false
}
}
}
}
},
"schema": {
"properties": {
"id": {
"description": "The ID of the reverse DNS record.",
"type": "integer"
},
"valid": {
"description": "Indicates if the reverse DNS record is valid.",
"enum": [
true,
false
],
"type": "boolean"
},
"validation_results": {
"description": "The specific results of the validation.",
"properties": {
"a_record": {
"properties": {
"reason": {
"description": "The reason the reverse DNS record could not be validated. Is `null` if the reverse DNS record was validated.",
"nullable": true,
"type": "string"
},
"valid": {
"description": "Indicates if the reverse DNS record could be validated.",
"enum": [
true,
false
],
"type": "boolean"
}
},
"required": [
"valid",
"reason"
],
"type": "object"
}
},
"type": "object"
}
},
"required": [
"id",
"valid",
"validation_results"
],
"type": "object"
}
}
},
"description": ""
},
"404": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"message": "Reverse DNS record not found."
}
]
}
}
},
"schema": {
"properties": {
"errors": {
"description": "The error messages for the failed validation.",
"items": {
"properties": {
"message": {
"description": "A message describing why the reverse DNS could not be validated.",
"type": "string"
}
},
"required": [
"message"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"errors"
],
"type": "object"
}
}
},
"description": "Unexpected error in API call. See HTTP response body for details."
},
"500": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"message": "internal error getting rDNS"
}
]
}
}
},
"schema": {
"properties": {
"errors": {
"description": "The error messages for the failed validation.",
"items": {
"properties": {
"message": {
"description": "A message describing why the IP whitelabel could not be validated.",
"type": "string"
}
},
"required": [
"message"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"errors"
],
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Validate a reverse DNS record",
"tags": [
"Reverse DNS"
],
"x-stoplight": {
"id": "POST_whitelabel-ips-id-validate",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/whitelabel/links": {
"get": {
"description": "**This endpoint allows you to retrieve all branded links**.\n\nYou can submit this request as one of your subusers if you include their ID in the `on-behalf-of` header in the request.",
"operationId": "GET_whitelabel-links",
"parameters": [
{
"description": "Limits the number of results returned per page.",
"in": "query",
"name": "limit",
"schema": {
"type": "integer"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": [
{
"default": true,
"dns": {
"domain_cname": {
"data": "sendgrid.net",
"host": "mail.example.com",
"type": "cname",
"valid": true
},
"owner_cname": {
"data": "sendgrid.net",
"host": "7.example.com",
"type": "cname",
"valid": true
}
},
"domain": "example.com",
"id": 1,
"legacy": false,
"subdomain": "mail",
"user_id": 7,
"username": "john@example.com",
"valid": true
},
{
"default": false,
"dns": {
"domain_cname": {
"data": "sendgrid.net",
"host": "news.example2.com",
"type": "cname",
"valid": true
},
"owner_cname": {
"data": "sendgrid.net",
"host": "8.example2.com",
"type": "cname",
"valid": false
}
},
"domain": "example2.com",
"id": 2,
"legacy": false,
"subdomain": "news",
"user_id": 8,
"username": "john@example.com",
"valid": false
}
]
}
},
"schema": {
"items": {
"$ref": "#/components/schemas/link_branding_200_response"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve all branded links",
"tags": [
"Link branding"
],
"x-stoplight": {
"id": "GET_whitelabel-links",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
},
"post": {
"description": "**This endpoint allows you to create a new branded link.**\n\nTo create the link branding, supply the root domain and, optionally, the subdomain — these go into separate fields in your request body. The root domain should match your FROM email address. If you provide a subdomain, it must be different from the subdomain you used for authenticating your domain.\n\nYou can submit this request as one of your subusers if you include their ID in the `on-behalf-of` header in the request.",
"operationId": "POST_whitelabel-links",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"default": true,
"domain": "example.com",
"subdomain": "mail"
},
"properties": {
"default": {
"description": "Indicates if you want to use this link branding as the default or fallback. When setting a new default, the existing default link branding will have its default status removed automatically.",
"enum": [
true,
false
],
"type": "boolean"
},
"domain": {
"description": "The root domain for the subdomain that you are creating the link branding for. This should match your FROM email address.",
"type": "string"
},
"subdomain": {
"description": "The subdomain to create the link branding for. Must be different from the subdomain you used for authenticating your domain.",
"type": "string"
}
},
"required": [
"domain"
],
"type": "object"
}
}
}
},
"responses": {
"201": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"default": false,
"dns": {
"domain_cname": {
"data": "sendgrid.net",
"host": "mail.example.com",
"type": "cname",
"valid": true
},
"owner_cname": {
"data": "sendgrid.net",
"host": "7.example.com",
"type": "cname",
"valid": true
}
},
"domain": "example.com",
"id": 1,
"legacy": false,
"subdomain": "mail",
"user_id": 7,
"username": "john@example.com",
"valid": true
}
}
},
"schema": {
"$ref": "#/components/schemas/link_branding_200_response"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Create a branded link",
"tags": [
"Link branding"
],
"x-stoplight": {
"id": "POST_whitelabel-links",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/whitelabel/links/default": {
"get": {
"description": "**This endpoint allows you to retrieve the default branded link.**\n\nThe default branded link is the actual URL to be used when sending messages. If you have more than one branded link, the default is determined by the following order:\n\n* The validated branded link marked as `default` (set when you call the \"Create a branded link\" endpoint or by calling the \"Update a branded link\" endpoint on an existing link)\n* Legacy branded links (migrated from the whitelabel wizard)\n* Default SendGrid-branded links (i.e., `100.ct.sendgrid.net`)\n\nYou can submit this request as one of your subusers if you include their ID in the `on-behalf-of` header in the request.",
"operationId": "GET_whitelabel-links-default",
"parameters": [
{
"description": "The domain to match against when finding the default branded link.",
"in": "query",
"name": "domain",
"schema": {
"type": "string"
}
},
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"default": false,
"dns": {
"domain_cname": {
"data": "sendgrid.net",
"host": "mail.example.com",
"type": "cname",
"valid": true
},
"owner_cname": {
"data": "sendgrid.net",
"host": "7.example.com",
"type": "cname",
"valid": true
}
},
"domain": "example.com",
"id": 1,
"legacy": false,
"subdomain": "mail",
"user_id": 7,
"username": "john@example.com",
"valid": true
}
}
},
"schema": {
"$ref": "#/components/schemas/link_branding_200_response"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve the default branded link",
"tags": [
"Link branding"
],
"x-stoplight": {
"id": "GET_whitelabel-links-default",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/whitelabel/links/subuser": {
"delete": {
"description": "**This endpoint allows you to take a branded link away from a subuser.**\n\nLink branding can be associated with subusers from the parent account. This functionality allows subusers to send mail using their parent's link branding. To associate link branding, the parent account must first create a branded link and validate it. The parent may then associate that branded link with a subuser via the API or the [Subuser Management page of the Twilio SendGrid App](https://app.sendgrid.com/settings/subusers).\n\nYour request will receive a response with a 204 status code if the disassociation was successful.",
"operationId": "DELETE_whitelabel-links-subuser",
"parameters": [
{
"description": "The username of the subuser account that you want to disassociate a branded link from.",
"in": "query",
"name": "username",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Disassociate a branded link from a subuser",
"tags": [
"Link branding"
],
"x-stoplight": {
"id": "DELETE_whitelabel-links-subuser",
"mock": {
"dynamic": false
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve the branded link associated with a subuser.**\n\nLink branding can be associated with subusers from the parent account. This functionality allows subusers to send mail using their parent's link branding. To associate link branding, the parent account must first create a branded link and then validate it. The parent may then associate that branded link with a subuser via the API or the [Subuser Management page of the Twilio SendGrid App](https://app.sendgrid.com/settings/subusers).",
"operationId": "GET_whitelabel-links-subuser",
"parameters": [
{
"description": "The username of the subuser to retrieve associated branded links for.",
"in": "query",
"name": "username",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"default": false,
"dns": {
"domain_cname": {
"data": "sendgrid.net",
"host": "mail.example.com",
"type": "cname",
"valid": true
},
"owner_cname": {
"data": "sendgrid.net",
"host": "7.example.com",
"type": "cname",
"valid": true
}
},
"domain": "example.com",
"id": 1,
"legacy": false,
"subdomain": "mail",
"user_id": 7,
"username": "john@example.com",
"valid": true
}
}
},
"schema": {
"$ref": "#/components/schemas/link_branding_200_response"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve a subuser's branded link",
"tags": [
"Link branding"
],
"x-stoplight": {
"id": "GET_whitelabel-links-subuser",
"mock": {
"dynamic": false,
"enabled": false,
"statusCode": 200
},
"public": true
}
}
},
"/whitelabel/links/{id}": {
"delete": {
"description": "**This endpoint allows you to delete a branded link.**\n\nYour request will receive a response with a 204 status code if the deletion was successful. The call does not return the link's details, so if you wish to record these make sure you call the \"Retrieve a branded link\" endpoint *before* you request its deletion.\n\nYou can submit this request as one of your subusers if you include their ID in the `on-behalf-of` header in the request.",
"operationId": "DELETE_whitelabel-links-id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"204": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Delete a branded link",
"tags": [
"Link branding"
],
"x-stoplight": {
"id": "DELETE_whitelabel-links-id",
"mock": {
"dynamic": false
},
"public": true
}
},
"get": {
"description": "**This endpoint allows you to retrieve a specific branded link by providing its ID.**\n\nYou can submit this request as one of your subusers if you include their ID in the `on-behalf-of` header in the request.",
"operationId": "GET_whitelabel-links-id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"default": false,
"dns": {
"domain_cname": {
"data": "sendgrid.net",
"host": "mail.example.com",
"type": "cname",
"valid": true
},
"owner_cname": {
"data": "sendgrid.net",
"host": "7.example.com",
"type": "cname",
"valid": true
}
},
"domain": "example.com",
"id": 1,
"legacy": false,
"subdomain": "mail",
"user_id": 7,
"username": "john@example.com",
"valid": true
}
}
},
"schema": {
"$ref": "#/components/schemas/link_branding_200_response"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Retrieve a branded link",
"tags": [
"Link branding"
],
"x-stoplight": {
"id": "GET_whitelabel-links-id",
"mock": {
"dynamic": false
},
"public": true
}
},
"parameters": [
{
"description": "The ID of the branded link you want to retrieve.",
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"patch": {
"description": "**This endpoint allows you to update a specific branded link. You can use this endpoint to change a branded link's default status.**\n\nYou can submit this request as one of your subusers if you include their ID in the `on-behalf-of` header in the request.",
"operationId": "PATCH_whitelabel-links-id",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"default": true
},
"properties": {
"default": {
"description": "Indicates if the branded link is set as the default. When setting a new default, the existing default link branding will have its default status removed automatically.",
"enum": [
true,
false
],
"type": "boolean"
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"default": true,
"dns": {
"domain_cname": {
"data": "sendgrid.net",
"host": "mail.example.com",
"type": "cname",
"valid": true
},
"owner_cname": {
"data": "sendgrid.net",
"host": "7.example.com",
"type": "cname",
"valid": true
}
},
"domain": "example.com",
"id": 1,
"legacy": false,
"subdomain": "mail",
"user_id": 7,
"username": "john@example.com",
"valid": true
}
}
},
"schema": {
"$ref": "#/components/schemas/link_branding_200_response"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Update a branded link",
"tags": [
"Link branding"
],
"x-stoplight": {
"id": "PATCH_whitelabel-links-id",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/whitelabel/links/{id}/validate": {
"parameters": [
{
"description": "The ID of the branded link that you want to validate.",
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"post": {
"description": "**This endpoint allows you to validate a branded link.**\n\nYou can submit this request as one of your subusers if you include their ID in the `on-behalf-of` header in the request.",
"operationId": "POST_whitelabel-links-id-validate",
"parameters": [
{
"$ref": "#/components/parameters/trait_onBehalfOfSubuser_on-behalf-of"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"id": 1,
"valid": true,
"validation_results": {
"domain_cname": {
"reason": "Expected CNAME to match \"sendgrid.net.\" but found \"example.com.\".",
"valid": false
},
"owner_cname": {
"reason": null,
"valid": true
}
}
}
}
},
"schema": {
"properties": {
"id": {
"description": "The ID of the branded link.",
"type": "integer"
},
"valid": {
"description": "Indicates if the link branding is valid.",
"enum": [
true,
false
],
"type": "boolean"
},
"validation_results": {
"description": "The individual validation results for each of the DNS records associated with this branded link.",
"properties": {
"domain_cname": {
"description": "The DNS record generated for the sending domain used for this branded link.",
"properties": {
"reason": {
"description": "Null if the DNS record is valid. If the DNS record is invalid, this will explain why.",
"nullable": true,
"type": "string"
},
"valid": {
"description": "Indicates if this DNS record is valid.",
"enum": [
true,
false
],
"type": "boolean"
}
},
"required": [
"valid",
"reason"
],
"type": "object"
},
"owner_cname": {
"description": "The DNS record created to verify the branded link.",
"properties": {
"reason": {
"description": "Null if valid. If the DNS record is invalid, this will explain why.",
"nullable": true,
"type": "string"
},
"valid": {
"description": "Indicates if the DNS record is valid.",
"enum": [
true,
false
],
"type": "boolean"
}
},
"required": [
"valid",
"reason"
],
"type": "object"
}
},
"required": [
"domain_cname"
],
"type": "object"
}
},
"required": [
"id",
"valid",
"validation_results"
],
"type": "object"
}
}
},
"description": ""
},
"500": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"errors": [
{
"message": "internal error getting CNAME"
}
]
}
}
},
"schema": {
"properties": {
"errors": {
"description": "The reasons why the validation failed.",
"items": {
"properties": {
"message": {
"description": "The reason why the link whitelabel could not be validated.",
"type": "string"
}
},
"required": [
"message"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"errors"
],
"type": "object"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Validate a branded link",
"tags": [
"Link branding"
],
"x-stoplight": {
"id": "POST_whitelabel-links-id-validate",
"mock": {
"dynamic": false
},
"public": true
}
}
},
"/whitelabel/links/{link_id}/subuser": {
"parameters": [
{
"description": "The ID of the branded link you want to associate.",
"in": "path",
"name": "link_id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"post": {
"description": "**This endpoint allows you to associate a branded link with a subuser account.**\n\nLink branding can be associated with subusers from the parent account. This functionality allows subusers to send mail using their parent's link branding. To associate link branding, the parent account must first create a branded link and validate it. The parent may then associate that branded link with a subuser via the API or the [Subuser Management page of the Twilio SendGrid App](https://app.sendgrid.com/settings/subusers).",
"operationId": "POST_whitelabel-links-link_id-subuser",
"requestBody": {
"content": {
"application/json": {
"schema": {
"example": {
"username": "jane@example.com"
},
"properties": {
"username": {
"description": "The username of the subuser account that you want to associate the branded link with.",
"type": "string"
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"response": {
"value": {
"default": false,
"dns": {
"domain_cname": {
"data": "sendgrid.net",
"host": "mail.example.com",
"type": "cname",
"valid": true
},
"owner_cname": {
"data": "sendgrid.net",
"host": "7.example.com",
"type": "cname",
"valid": true
}
},
"domain": "example.com",
"id": 1,
"legacy": false,
"subdomain": "mail",
"user_id": 7,
"username": "john@example.com",
"valid": true
}
}
},
"schema": {
"$ref": "#/components/schemas/link_branding_200_response"
}
}
},
"description": ""
}
},
"security": [
{
"Authorization": []
}
],
"summary": "Associate a branded link with a subuser",
"tags": [
"Link branding"
],
"x-stoplight": {
"id": "POST_whitelabel-links-linkid-subuser",
"mock": {
"dynamic": false
},
"public": true
}
}
}
},
"servers": [
{
"url": "http://api.sendgrid.com/v3"
}
],
"tags": [
{
"name": "Subuser Monitor Settings"
},
{
"name": "segmenting contacts"
},
{
"name": "Single Sign-On Settings"
},
{
"name": "Settings - Partner"
},
{
"name": "Contacts API - Recipients"
},
{
"name": "Settings - Inbound Parse"
},
{
"name": "Segmenting Contacts"
},
{
"name": "IP Access Management"
},
{
"name": "Messages"
},
{
"name": "Suppressions - Unsubscribe Groups"
},
{
"name": "Email CNAME records"
},
{
"name": "CSV (UI only)"
},
{
"name": "Bounces API"
},
{
"name": "Designs API"
},
{
"name": "Custom Fields"
},
{
"name": "Segmenting Contacts V2 - Beta"
},
{
"name": "Subuser Statistics"
},
{
"name": "Send Test Email"
},
{
"name": "IP Addresses"
},
{
"name": "Query"
},
{
"name": "V3"
},
{
"name": "Invalid Emails API"
},
{
"name": "Webhooks"
},
{
"name": "Suppressions - Global Suppressions"
},
{
"name": "Settings - Tracking"
},
{
"name": "Contacts API - Lists"
},
{
"name": "Marketing Campaigns Stats"
},
{
"name": "API Key Permissions"
},
{
"name": "Domain Authentication"
},
{
"name": "Sender Identities API"
},
{
"name": "Single Sign-On Teammates"
},
{
"name": "Mail Send"
},
{
"name": "Settings - Enforced TLS"
},
{
"name": "IP Warmup"
},
{
"name": "Categories"
},
{
"name": "Campaigns API"
},
{
"name": "Teammates"
},
{
"name": "Sender Verification"
},
{
"name": "Single Sends"
},
{
"name": "Blocks API"
},
{
"name": "Contacts"
},
{
"name": "Certificates"
},
{
"name": "Contacts API - Segments"
},
{
"name": "Senders"
},
{
"name": "Contacts API - Custom Fields"
},
{
"name": "Spam Reports API"
},
{
"name": "Subusers API"
},
{
"name": "Link branding"
},
{
"name": "Suppressions - Suppressions"
},
{
"name": "Users API"
},
{
"name": "Settings - Mail"
},
{
"name": "API Keys"
},
{
"name": "Transactional Templates"
},
{
"name": "Reverse DNS"
},
{
"name": "Email Address Validation"
},
{
"name": "Transactional Templates Versions"
},
{
"name": "Lists"
},
{
"name": "Cancel Scheduled Sends"
},
{
"name": "Stats"
},
{
"name": "Alerts"
},
{
"name": "IP Pools"
}
],
"x-stoplight": {
"afterScript": "function (ctx, request, response) {\n // REMOVE WITH CAUTION. OR AT LEAST, JUST LEAVE IT COMMENTED OUT.\n // Removing this means your endpoint AFTER scripts will not be run!\n SL.runEndpoint();\n \n// if (response.body.get().count() > 1) {\n// request.hijack(200, 'application/json', {foo: 'bear'})\n// }\n\n \n //make this always available\n //SL.utilities.Audit_LogDataInResponse(ctx, request, response);\n \n //if (request.url.query.get('test')) {\n // SL.utilities.RunSpecifiedTests(ctx, request, response);\n //}\n \n // ELMER: Remove this, it logs only DELETE calls\n // if (JSON.parse(request.header.get('X-Mock')) == response.statusCode.get()) {\n // ctx.log.set(false);\n // } else {\n // ctx.log.set(true);\n //}\n}",
"beforeScript": "function (ctx, request) {\n \n request.header.set('User-Agent', 'sendgrid/stoplightdocs');\n \n // REMOVE WITH CAUTION. OR AT LEAST, JUST LEAVE IT COMMENTED OUT.\n // Removing this means your endpoint before scripts will not be run!\n SL.runEndpoint();\n \n //adds the Accept header to every request\n // request.header.set(\"accept\", \"application/json\")\n \n //request.header.set(\"foo\", \"bear\");\n //request.header.add(\"bar\", \"cat\");\n\n // if (request.url.query.get('accept')) {\n // request.header.set('Accept', 'this is a bad header');\n // }\n \n // if (request.url.query.get('behalf')) {\n // request.header.set('on-behalf-of', 'subuser2');\n // }\n \n // For example, adding ?mock=200 to a request url will enable mocking,\n // using the example endpoint response for the 200 status code.\n // var mock = request.url.query.get(\"mock\")\n // if (mock) {\n // ctx.mock.set(true, mock)\n //}\n \n // For example, adding the header X-Mock: 200 will enable mocking,\n // using the example endpoint response for the 200 status code.\n // var mock = request.header.get(\"X-Mock\");\n // if (mock) {\n\t // ctx.mock.set(true, mock);\n\t // ctx.learn.set(false);\n // }\n}",
"functions": {},
"mock": {
"dynamic": false,
"enabled": false
},
"textSections": {
"api-authentication": {
"content": "## Authorization Header\n\nTo authenticate, add an `Authorization` header to your API request that contains an [API Key](https://sendgrid.com/docs/API_Reference/Web_API_v3/API_Keys/index.html).\n\n## API Keys\n\nSendGrid’s Web API v3 supports the use of API Keys. API Keys allow you to use another method of authentication separate from your account username and password. API Keys add an additional layer of security for your account and can be assigned [specific permissions](https://sendgrid.com/docs/API_Reference/Web_API_v3/API_Keys/api_key_permissions_list.html) to limit which areas of your account they may be used to access. [API Keys can be generated in your account](https://app.sendgrid.com/settings/api_keys). To use keys, you must set a plain text header named “Authorization” with the contents of the header being “Bearer XXX” where XXX is your API Secret Key.\n\n### Example Header\n\n```bash\nGET https://api.sendgrid.com/v3/resource HTTP/1.1\nAuthorization: Bearer Your.API.Key-HERE\n```\n\n```bash\ncurl -X \"GET\" \"https://api.sendgrid.com/v3/templates\" -H \"Authorization: Bearer Your.API.Key-HERE\" -H \"Content-Type: application/json\"\n```",
"id": "api-authentication",
"name": "Authentication",
"public": true
},
"api-authorization": {
"content": "# API Key Permissions List\n\nAPI Keys can be used to authenticate the use of SendGrid’s v3 Web API, or the [Mail API endpoint](https://sendgrid.com/docs/api-reference/). API Keys may be assigned certain permissions, or scopes, that limit which API endpoints they are able to access. For a more detailed explanation of how you can use API Key permissions, please visit our [API Keys docs](https://sendgrid.com/docs/ui/account-and-settings/api-keys/). \n\nThe following is a complete list of all possible permissions that you may assign to an API Key.\n\n>When updating a key to include `user` or `subuser` scopes, use basic authenitcation.\n\n## Table of Contents\n\n\n\n\n Alerts \n\n```json\n\"scopes\": [\n \"alerts.create\",\n \"alerts.delete\",\n \"alerts.read\",\n \"alerts.update\"\n]\n```\n\n API Keys \n\n```json\n\"scopes\": [\n \"api_keys.create\",\n \"api_keys.delete\",\n \"api_keys.read\",\n \"api_keys.update\"\n]\n```\n\n ASM Groups \n\n```json\n\"scopes\": [\n \"asm.groups.create\",\n \"asm.groups.delete\",\n \"asm.groups.read\",\n \"asm.groups.update\"\n]\n```\n\n\n Billing \n\n```json\n\"scopes\": [\n \"billing.create\",\n \"billing.delete\",\n \"billing.read\",\n \"billing.update\"\n]\n```\n\n\n**Billing permissions are mutually exclusive from all other permissions. An API Key can have *either* Billing Permissions *or* any other set of Permissions but not *both*.**\n\n\n Categories \n\n```json\n\"scopes\": [\n \"categories.create\",\n \"categories.delete\",\n \"categories.read\",\n \"categories.update\",\n \"categories.stats.read\",\n \"categories.stats.sums.read\"\n]\n```\n\n Stats \n\n```json\n\"scopes\": [\n \"email_activity.read\",\n \"stats.read\",\n \"stats.global.read\",\n \"browsers.stats.read\",\n \"devices.stats.read\",\n \"geo.stats.read\",\n \"mailbox_providers.stats.read\",\n \"clients.desktop.stats.read\",\n \"clients.phone.stats.read\",\n \"clients.stats.read\",\n \"clients.tablet.stats.read\",\n \"clients.webmail.stats.read\"\n]\n```\n\n IPs \n\n```json\n\"scopes\": [\n \"ips.assigned.read\",\n \"ips.read\",\n \"ips.pools.create\",\n \"ips.pools.delete\",\n \"ips.pools.read\",\n \"ips.pools.update\",\n \"ips.pools.ips.create\",\n \"ips.pools.ips.delete\",\n \"ips.pools.ips.read\",\n \"ips.pools.ips.update\",\n \"ips.warmup.create\",\n \"ips.warmup.delete\",\n \"ips.warmup.read\",\n \"ips.warmup.update\"\n]\n```\n\n Mail Settings \n\n```json\n\"scopes\": [\n \"mail_settings.address_whitelist.read\",\n \"mail_settings.address_whitelist.update\",\n \"mail_settings.bounce_purge.read\",\n \"mail_settings.bounce_purge.update\",\n \"mail_settings.footer.read\",\n \"mail_settings.footer.update\",\n \"mail_settings.forward_bounce.read\",\n \"mail_settings.forward_bounce.update\",\n \"mail_settings.forward_spam.read\",\n \"mail_settings.forward_spam.update\",\n \"mail_settings.template.read\",\n \"mail_settings.template.update\"\n]\n```\n\n Mail \n\n```json\n\"scopes\": [\n \"mail.batch.create\",\n \"mail.batch.delete\",\n \"mail.batch.read\",\n \"mail.batch.update\",\n \"mail.send\"\n]\n```\n\n Marketing Campaigns \n\n```json\n\"scopes\": [\n \"marketing_campaigns.create\",\n \"marketing_campaigns.delete\",\n \"marketing_campaigns.read\",\n \"marketing_campaigns.update\"\n]\n```\n\n\n Partner Settings \n\n```json\n\"scopes\": [\n \"partner_settings.new_relic.read\",\n \"partner_settings.new_relic.update\",\n \"partner_settings.read\"\n]\n```\n\n Scheduled Sends \n\n```json\n\"scopes\": [\n \"user.scheduled_sends.create\",\n \"user.scheduled_sends.delete\",\n \"user.scheduled_sends.read\",\n \"user.scheduled_sends.update\"\n]\n```\n\n Subusers \n\n```json\n\"scopes\": [\n \"subusers.create\",\n \"subusers.delete\",\n \"subusers.read\",\n \"subusers.update\",\n \"subusers.credits.create\",\n \"subusers.credits.delete\",\n \"subusers.credits.read\",\n \"subusers.credits.update\",\n \"subusers.credits.remaining.create\",\n \"subusers.credits.remaining.delete\",\n \"subusers.credits.remaining.read\",\n \"subusers.credits.remaining.update\",\n \"subusers.monitor.create\",\n \"subusers.monitor.delete\",\n \"subusers.monitor.read\",\n \"subusers.monitor.update\",\n \"subusers.reputations.read\",\n \"subusers.stats.read\",\n \"subusers.stats.monthly.read\",\n \"subusers.stats.sums.read\"\n \"subusers.summary.read\"\n]\n```\n\n Suppressions \n\n```json\n\"scopes\": [\n \"suppression.create\",\n \"suppression.delete\",\n \"suppression.read\",\n \"suppression.update\",\n \"suppression.bounces.create\",\n \"suppression.bounces.read\",\n \"suppression.bounces.update\",\n \"suppression.bounces.delete\",\n \"suppression.blocks.create\",\n \"suppression.blocks.read\",\n \"suppression.blocks.update\",\n \"suppression.blocks.delete\",\n \"suppression.invalid_emails.create\",\n \"suppression.invalid_emails.read\",\n \"suppression.invalid_emails.update\",\n \"suppression.invalid_emails.delete\",\n \"suppression.spam_reports.create\",\n \"suppression.spam_reports.read\",\n \"suppression.spam_reports.update\",\n \"suppression.spam_reports.delete\",\n \"suppression.unsubscribes.create\",\n \"suppression.unsubscribes.read\",\n \"suppression.unsubscribes.update\",\n \"suppression.unsubscribes.delete\"\n]\n```\n\n Teammates \n\n```json\n\"scopes\": [\n \"teammates.create\",\n \"teammates.read\",\n \"teammates.update\",\n \"teammates.delete\"\n]\n```\n\n Templates \n\n```json\n\"scopes\": [\n \"templates.create\",\n \"templates.delete\",\n \"templates.read\",\n \"templates.update\",\n \"templates.versions.activate.create\",\n \"templates.versions.activate.delete\",\n \"templates.versions.activate.read\",\n \"templates.versions.activate.update\",\n \"templates.versions.create\",\n \"templates.versions.delete\",\n \"templates.versions.read\",\n \"templates.versions.update\"\n]\n```\n\n Tracking \n\n```json\n\"scopes\": [\n \"tracking_settings.click.read\",\n \"tracking_settings.click.update\",\n \"tracking_settings.google_analytics.read\",\n \"tracking_settings.google_analytics.update\",\n \"tracking_settings.open.read\",\n \"tracking_settings.open.update\",\n \"tracking_settings.read\",\n \"tracking_settings.subscription.read\",\n \"tracking_settings.subscription.update\"\n]\n```\n\n User Settings \n\n```json\n\"scopes\": [\n \"user.account.read\",\n \"user.credits.read\",\n \"user.email.create\",\n \"user.email.delete\",\n \"user.email.read\",\n \"user.email.update\",\n \"user.multifactor_authentication.create\",\n \"user.multifactor_authentication.delete\",\n \"user.multifactor_authentication.read\",\n \"user.multifactor_authentication.update\",\n \"user.password.read\",\n \"user.password.update\",\n \"user.profile.read\",\n \"user.profile.update\",\n \"user.settings.enforced_tls.read\",\n \"user.settings.enforced_tls.update\",\n \"user.timezone.read\",\n \"user.timezone.update\",\n \"user.username.read\",\n \"user.username.update\"\n]\n```\n\n Webhook \n\n```json\n\"scopes\": [\n \"user.webhooks.event.settings.read\",\n \"user.webhooks.event.settings.update\",\n \"user.webhooks.event.test.create\",\n \"user.webhooks.event.test.read\",\n \"user.webhooks.event.test.update\",\n \"user.webhooks.parse.settings.create\",\n \"user.webhooks.parse.settings.delete\",\n \"user.webhooks.parse.settings.read\",\n \"user.webhooks.parse.settings.update\",\n \"user.webhooks.parse.stats.read\"\n]\n```\n\n Domain Authentication (formerly Whitelabel) \n\n```json\n\"scopes\": [\n \"whitelabel.create\",\n \"whitelabel.delete\",\n \"whitelabel.read\",\n \"whitelabel.update\"\n]\n```\n\n Reverse DNS (formerly Whitelist) \n\n```json\n\"scopes\": [\n \"access_settings.activity.read\",\n \"access_settings.whitelist.create\",\n \"access_settings.whitelist.delete\",\n \"access_settings.whitelist.read\",\n \"access_settings.whitelist.update\"\n]\n```\n\n Admin API Key Scopes \n\nBelow is a complete list of every API Key scope to be given to an admin level API Key.\n\n```json\n\"scopes\": [\n \"access_settings.activity.read\",\n \"access_settings.whitelist.create\",\n \"access_settings.whitelist.delete\",\n \"access_settings.whitelist.read\",\n \"access_settings.whitelist.update\",\n \"alerts.create\",\n \"alerts.delete\",\n \"alerts.read\",\n \"alerts.update\",\n \"api_keys.create\",\n \"api_keys.delete\",\n \"api_keys.read\",\n \"api_keys.update\",\n \"asm.groups.create\",\n \"asm.groups.delete\",\n \"asm.groups.read\",\n \"asm.groups.update\",\n \"billing.create\",\n \"billing.delete\",\n \"billing.read\",\n \"billing.update\",\n \"browsers.stats.read\",\n \"categories.create\",\n \"categories.delete\",\n \"categories.read\",\n \"categories.stats.read\",\n \"categories.stats.sums.read\",\n \"categories.update\",\n \"clients.desktop.stats.read\",\n \"clients.phone.stats.read\",\n \"clients.stats.read\",\n \"clients.tablet.stats.read\",\n \"clients.webmail.stats.read\",\n \"devices.stats.read\",\n \"email_activity.read\",\n \"geo.stats.read\",\n \"ips.assigned.read\",\n \"ips.pools.create\",\n \"ips.pools.delete\",\n \"ips.pools.ips.create\",\n \"ips.pools.ips.delete\",\n \"ips.pools.ips.read\",\n \"ips.pools.ips.update\",\n \"ips.pools.read\",\n \"ips.pools.update\",\n \"ips.read\",\n \"ips.warmup.create\",\n \"ips.warmup.delete\",\n \"ips.warmup.read\",\n \"ips.warmup.update\",\n \"mail_settings.address_whitelist.read\",\n \"mail_settings.address_whitelist.update\",\n \"mail_settings.bounce_purge.read\",\n \"mail_settings.bounce_purge.update\",\n \"mail_settings.footer.read\",\n \"mail_settings.footer.update\",\n \"mail_settings.forward_bounce.read\",\n \"mail_settings.forward_bounce.update\",\n \"mail_settings.forward_spam.read\",\n \"mail_settings.forward_spam.update\",\n \"mail_settings.plain_content.read\",\n \"mail_settings.plain_content.update\",\n \"mail_settings.read\",,\n \"mail_settings.template.read\",\n \"mail_settings.template.update\",\n \"mail.batch.create\",\n \"mail.batch.delete\",\n \"mail.batch.read\",\n \"mail.batch.update\",\n \"mail.send\",\n \"mailbox_providers.stats.read\",\n \"marketing_campaigns.create\",\n \"marketing_campaigns.delete\",\n \"marketing_campaigns.read\",\n \"marketing_campaigns.update\",\n \"partner_settings.new_relic.read\",\n \"partner_settings.new_relic.update\",\n \"partner_settings.read\",\n \"stats.global.read\",\n \"stats.read\",\n \"subusers.create\",\n \"subusers.credits.create\",\n \"subusers.credits.delete\",\n \"subusers.credits.read\",\n \"subusers.credits.remaining.create\",\n \"subusers.credits.remaining.delete\",\n \"subusers.credits.remaining.read\",\n \"subusers.credits.remaining.update\",\n \"subusers.credits.update\",\n \"subusers.delete\",\n \"subusers.monitor.create\",\n \"subusers.monitor.delete\",\n \"subusers.monitor.read\",\n \"subusers.monitor.update\",\n \"subusers.read\",\n \"subusers.reputations.read\",\n \"subusers.stats.monthly.read\",\n \"subusers.stats.read\",\n \"subusers.stats.sums.read\",\n \"subusers.summary.read\",\n \"subusers.update\",\n \"suppression.blocks.create\",\n \"suppression.blocks.delete\",\n \"suppression.blocks.read\",\n \"suppression.blocks.update\",\n \"suppression.bounces.create\",\n \"suppression.bounces.delete\",\n \"suppression.bounces.read\",\n \"suppression.bounces.update\",\n \"suppression.create\",\n \"suppression.delete\",\n \"suppression.invalid_emails.create\",\n \"suppression.invalid_emails.delete\",\n \"suppression.invalid_emails.read\",\n \"suppression.invalid_emails.update\",\n \"suppression.read\",\n \"suppression.spam_reports.create\",\n \"suppression.spam_reports.delete\",\n \"suppression.spam_reports.read\",\n \"suppression.spam_reports.update\",\n \"suppression.unsubscribes.create\",\n \"suppression.unsubscribes.delete\",\n \"suppression.unsubscribes.read\",\n \"suppression.unsubscribes.update\",\n \"suppression.update\",\n \"teammates.create\",\n \"teammates.read\",\n \"teammates.update\",\n \"teammates.delete\",\n \"templates.create\",\n \"templates.delete\",\n \"templates.read\",\n \"templates.update\",\n \"templates.versions.activate.create\",\n \"templates.versions.activate.delete\",\n \"templates.versions.activate.read\",\n \"templates.versions.activate.update\",\n \"templates.versions.create\",\n \"templates.versions.delete\",\n \"templates.versions.read\",\n \"templates.versions.update\",\n \"tracking_settings.click.read\",\n \"tracking_settings.click.update\",\n \"tracking_settings.google_analytics.read\",\n \"tracking_settings.google_analytics.update\",\n \"tracking_settings.open.read\",\n \"tracking_settings.open.update\",\n \"tracking_settings.read\",\n \"tracking_settings.subscription.read\",\n \"tracking_settings.subscription.update\",\n \"user.account.read\",\n \"user.credits.read\",\n \"user.email.create\",\n \"user.email.delete\",\n \"user.email.read\",\n \"user.email.update\",\n \"user.multifactor_authentication.create\",\n \"user.multifactor_authentication.delete\",\n \"user.multifactor_authentication.read\",\n \"user.multifactor_authentication.update\",\n \"user.password.read\",\n \"user.password.update\",\n \"user.profile.read\",\n \"user.profile.update\",\n \"user.scheduled_sends.create\",\n \"user.scheduled_sends.delete\",\n \"user.scheduled_sends.read\",\n \"user.scheduled_sends.update\",\n \"user.settings.enforced_tls.read\",\n \"user.settings.enforced_tls.update\",\n \"user.timezone.read\",\n \"user.username.read\",\n \"user.username.update\",\n \"user.webhooks.event.settings.read\",\n \"user.webhooks.event.settings.update\",\n \"user.webhooks.event.test.create\",\n \"user.webhooks.event.test.read\",\n \"user.webhooks.event.test.update\",\n \"user.webhooks.parse.settings.create\",\n \"user.webhooks.parse.settings.delete\",\n \"user.webhooks.parse.settings.read\",\n \"user.webhooks.parse.settings.update\",\n \"user.webhooks.parse.stats.read\",\n \"whitelabel.create\",\n \"whitelabel.delete\",\n \"whitelabel.read\",\n \"whitelabel.update\"\n]\n```",
"id": "api-authorization",
"name": "Authorization",
"public": true
},
"api-errors": {
"content": "Sometimes your API call will generate an error. Here you will find additional information about what to expect if you don’t format your request properly, or we fail to properly process your request.\n\n## Response Codes\n\n| **Status Code** | **Description** |\n|---|---|\n| 400 | Bad request|\n| 401 | Requires authentication |\n| 406 | Missing Accept header. For example: `Accept: application/json` |\n| 429 | Too many requests/Rate limit exceeded |\n| 500 | Internal server error |\n\n## Failed Requests\n\nThe general format guidelines are displayed when the accompanying status code is returned.\n\n```bash\nGET https://api.sendgrid.com/v3/resource HTTP/1.1\n```\n\n```json\nHTTP/1.1 400 BAD REQUEST\nContent-Type: application/json\n\n{\n \"errors\": [\n {\"field\": \"identifier1\", \"message\": \"error message explained\"},\n {\"field\": \"identifier2\", \"message\": \"error message explained\"},\n {\"field\": \"identifier3\", \"message\": \"error message explained\"},\n ]\n}\n```",
"id": "api-errors",
"name": "Errors",
"public": true
},
"api-key-permissions": {
"content": "API Keys can be used to authenticate the use of [SendGrid’s v3 API](https://sendgrid.api-docs.io/v3.0/how-to-use-the-sendgrid-v3-api/api-authorization). API Keys may be assigned certain permissions, or scopes, that limit which API endpoints they are able to access.\n\nThe following is a complete list of all possible permissions that you may assign to an API Key.\n\n* [Admin API Key Permissions](#Admin-API-Key-Permissions)\n* [Alerts](#Alerts)\n* [API Keys](#API-Keys)\n* [ASM Groups](#ASM-Groups)\n* [Billing](#Billing)\n* [Categories](#Categories)\n* [Clients](#Clients)\n* [Credentials](#Credentials)\n* [IPs](#IPs)\n* [Mail Settings](#Mail-Settings)\n* [Mail](#Mail)\n* [Marketing Campaigns](#Marketing-Campaigns)\n* [Partner Settings](#Partner-Settings)\n* [Scheduled Sends](#Scheduled-Sends)\n* [Stats](#Stats)\n* [Subusers](#Subusers)\n* [Suppressions](#Suppressions)\n* [Teammates](#Teammates)\n* [Templates](#Templates)\n* [Tracking](#Tracking)\n* [User Settings](#User-Settings)\n* [Webhook](#Webhook)\n* [Domain Authentication](#Domain-Authentication)\n* [Reverse DNS](#Reverse-DNS)\n\n \n\n Admin API Key Permissions\n \n\nBelow is a complete list of every API Key permission that should be given to an admin level API Key.\n\n```json\n\"scopes\": [\n \"access_settings.activity.read\",\n \"access_settings.whitelist.create\",\n \"access_settings.whitelist.delete\",\n \"access_settings.whitelist.read\",\n \"access_settings.whitelist.update\",\n \"alerts.create\",\n \"alerts.delete\",\n \"alerts.read\",\n \"alerts.update\",\n \"api_keys.create\",\n \"api_keys.delete\",\n \"api_keys.read\",\n \"api_keys.update\",\n \"asm.groups.create\",\n \"asm.groups.delete\",\n \"asm.groups.read\",\n \"asm.groups.update\",\n \"billing.create\",\n \"billing.delete\",\n \"billing.read\",\n \"billing.update\",\n \"browsers.stats.read\",\n \"categories.create\",\n \"categories.delete\",\n \"categories.read\",\n \"categories.stats.read\",\n \"categories.stats.sums.read\",\n \"categories.update\",\n \"clients.desktop.stats.read\",\n \"clients.phone.stats.read\",\n \"clients.stats.read\",\n \"clients.tablet.stats.read\",\n \"clients.webmail.stats.read\",\n \"credentials.create\",\n \"credentials.delete\",\n \"credentials.read\",\n \"credentials.update\",\n \"devices.stats.read\",\n \"email_activity.read\",\n \"geo.stats.read\",\n \"ips.assigned.read\",\n \"ips.pools.create\",\n \"ips.pools.delete\",\n \"ips.pools.ips.create\",\n \"ips.pools.ips.delete\",\n \"ips.pools.ips.read\",\n \"ips.pools.ips.update\",\n \"ips.pools.read\",\n \"ips.pools.update\",\n \"ips.read\",\n \"ips.warmup.create\",\n \"ips.warmup.delete\",\n \"ips.warmup.read\",\n \"ips.warmup.update\",\n \"mail_settings.address_whitelist.read\",\n \"mail_settings.address_whitelist.update\",\n \"mail_settings.bcc.read\",\n \"mail_settings.bcc.update\",\n \"mail_settings.bounce_purge.read\",\n \"mail_settings.bounce_purge.update\",\n \"mail_settings.footer.read\",\n \"mail_settings.footer.update\",\n \"mail_settings.forward_bounce.read\",\n \"mail_settings.forward_bounce.update\",\n \"mail_settings.forward_spam.read\",\n \"mail_settings.forward_spam.update\",\n \"mail_settings.plain_content.read\",\n \"mail_settings.plain_content.update\",\n \"mail_settings.read\",\n \"mail_settings.spam_check.read\",\n \"mail_settings.spam_check.update\",\n \"mail_settings.template.read\",\n \"mail_settings.template.update\",\n \"mail.batch.create\",\n \"mail.batch.delete\",\n \"mail.batch.read\",\n \"mail.batch.update\",\n \"mail.send\",\n \"mailbox_providers.stats.read\",\n \"marketing_campaigns.create\",\n \"marketing_campaigns.delete\",\n \"marketing_campaigns.read\",\n \"marketing_campaigns.update\",\n \"newsletter.create\",\n \"newsletter.delete\",\n \"newsletter.read\",\n \"newsletter.update\",\n \"partner_settings.new_relic.read\",\n \"partner_settings.new_relic.update\",\n \"partner_settings.read\",\n \"partner_settings.sendwithus.read\",\n \"partner_settings.sendwithus.update\",\n \"stats.global.read\",\n \"stats.read\",\n \"subusers.create\",\n \"subusers.credits.create\",\n \"subusers.credits.delete\",\n \"subusers.credits.read\",\n \"subusers.credits.remaining.create\",\n \"subusers.credits.remaining.delete\",\n \"subusers.credits.remaining.read\",\n \"subusers.credits.remaining.update\",\n \"subusers.credits.update\",\n \"subusers.delete\",\n \"subusers.monitor.create\",\n \"subusers.monitor.delete\",\n \"subusers.monitor.read\",\n \"subusers.monitor.update\",\n \"subusers.read\",\n \"subusers.reputations.read\",\n \"subusers.stats.monthly.read\",\n \"subusers.stats.read\",\n \"subusers.stats.sums.read\",\n \"subusers.summary.read\",\n \"subusers.update\",\n \"suppression.blocks.create\",\n \"suppression.blocks.delete\",\n \"suppression.blocks.read\",\n \"suppression.blocks.update\",\n \"suppression.bounces.create\",\n \"suppression.bounces.delete\",\n \"suppression.bounces.read\",\n \"suppression.bounces.update\",\n \"suppression.create\",\n \"suppression.delete\",\n \"suppression.invalid_emails.create\",\n \"suppression.invalid_emails.delete\",\n \"suppression.invalid_emails.read\",\n \"suppression.invalid_emails.update\",\n \"suppression.read\",\n \"suppression.spam_reports.create\",\n \"suppression.spam_reports.delete\",\n \"suppression.spam_reports.read\",\n \"suppression.spam_reports.update\",\n \"suppression.unsubscribes.create\",\n \"suppression.unsubscribes.delete\",\n \"suppression.unsubscribes.read\",\n \"suppression.unsubscribes.update\",\n \"suppression.update\",\n \"templates.create\",\n \"templates.delete\",\n \"templates.read\",\n \"templates.update\",\n \"templates.versions.activate.create\",\n \"templates.versions.activate.delete\",\n \"templates.versions.activate.read\",\n \"templates.versions.activate.update\",\n \"templates.versions.create\",\n \"templates.versions.delete\",\n \"templates.versions.read\",\n \"templates.versions.update\",\n \"tracking_settings.click.read\",\n \"tracking_settings.click.update\",\n \"tracking_settings.google_analytics.read\",\n \"tracking_settings.google_analytics.update\",\n \"tracking_settings.open.read\",\n \"tracking_settings.open.update\",\n \"tracking_settings.read\",\n \"tracking_settings.subscription.read\",\n \"tracking_settings.subscription.update\",\n \"user.account.read\",\n \"user.credits.read\",\n \"user.email.create\",\n \"user.email.delete\",\n \"user.email.read\",\n \"user.email.update\",\n \"user.multifactor_authentication.create\",\n \"user.multifactor_authentication.delete\",\n \"user.multifactor_authentication.read\",\n \"user.multifactor_authentication.update\",\n \"user.password.read\",\n \"user.password.update\",\n \"user.profile.read\",\n \"user.profile.update\",\n \"user.scheduled_sends.create\",\n \"user.scheduled_sends.delete\",\n \"user.scheduled_sends.read\",\n \"user.scheduled_sends.update\",\n \"user.settings.enforced_tls.read\",\n \"user.settings.enforced_tls.update\",\n \"user.timezone.read\",\n \"user.username.read\",\n \"user.username.update\",\n \"user.webhooks.event.settings.read\",\n \"user.webhooks.event.settings.update\",\n \"user.webhooks.event.test.create\",\n \"user.webhooks.event.test.read\",\n \"user.webhooks.event.test.update\",\n \"user.webhooks.parse.settings.create\",\n \"user.webhooks.parse.settings.delete\",\n \"user.webhooks.parse.settings.read\",\n \"user.webhooks.parse.settings.update\",\n \"user.webhooks.parse.stats.read\",\n \"whitelabel.create\",\n \"whitelabel.delete\",\n \"whitelabel.read\",\n \"whitelabel.update\"\n]\n```\n\n \n\n Alerts\n \n\n```json\n\"scopes\": [\n \"alerts.create\",\n \"alerts.delete\",\n \"alerts.read\",\n \"alerts.update\"\n]\n```\n\n \n\n API Keys\n \n\n```json\n\"scopes\": [\n \"api_keys.create\",\n \"api_keys.delete\",\n \"api_keys.read\",\n \"api_keys.update\"\n]\n```\n \n\n ASM Groups\n \n\n```json\n\"scopes\": [\n \"asm.groups.create\",\n \"asm.groups.delete\",\n \"asm.groups.read\",\n \"asm.groups.update\"\n]\n```\na>\n\n Billing\n \n\n**Billing permissions are mutually exclusive from all other permissions. An API Key can have *either* Billing Permissions *or* any other set of Permissions but not *both*.**\n\n```json\n\"scopes\": [\n \"billing.create\",\n \"billing.delete\",\n \"billing.read\",\n \"billing.update\"\n]\n```\n\n \n\n Categories\n \n\n```json\n\"scopes\": [\n \"categories.create\",\n \"categories.delete\",\n \"categories.read\",\n \"categories.update\",\n \"categories.stats.read\",\n \"categories.stats.sums.read\"\n]\n```\n\n \n\n Clients\n \n\n```json\n\"scopes\": [\n \"clients.desktop.stats.read\",\n \"clients.phone.stats.read\",\n \"clients.stats.read\",\n \"clients.tablet.stats.read\",\n \"clients.webmail.stats.read\"\n]\n```\n\n \n\n Credentials\n \n\n```json\n\"scopes\": [\n \"credentials.create\",\n \"credentials.delete\",\n \"credentials.read\",\n \"credentials.update\"\n]\n```\n\n \n\n IPs\n \n\n```json\n\"scopes\": [\n \"ips.assigned.read\",\n \"ips.read\",\n \"ips.pools.create\",\n \"ips.pools.delete\",\n \"ips.pools.read\",\n \"ips.pools.update\",\n \"ips.pools.ips.create\",\n \"ips.pools.ips.delete\",\n \"ips.pools.ips.read\",\n \"ips.pools.ips.update\",\n \"ips.warmup.create\",\n \"ips.warmup.delete\",\n \"ips.warmup.read\",\n \"ips.warmup.update\"\n]\n```\n\n \n\n Mail Settings\n \n\n```json\n\"scopes\": [\n \"mail_settings.address_whitelist.read\",\n \"mail_settings.address_whitelist.update\",\n \"mail_settings.bcc.read\",\n \"mail_settings.bcc.update\",\n \"mail_settings.bounce_purge.read\",\n \"mail_settings.bounce_purge.update\",\n \"mail_settings.footer.read\",\n \"mail_settings.footer.update\",\n \"mail_settings.forward_bounce.read\",\n \"mail_settings.forward_bounce.update\",\n \"mail_settings.forward_spam.read\",\n \"mail_settings.forward_spam.update\",\n \"mail_settings.plain_content.read\",\n \"mail_settings.plain_content.update\",\n \"mail_settings.read\",\n \"mail_settings.spam_check.read\",\n \"mail_settings.spam_check.update\",\n \"mail_settings.template.read\",\n \"mail_settings.template.update\"\n]\n```\n\n \n\n Mail\n \n\n```json\n\"scopes\": [\n \"mail.batch.create\",\n \"mail.batch.delete\",\n \"mail.batch.read\",\n \"mail.batch.update\",\n \"mail.send\"\n]\n```\n\n \n\n Marketing Campaigns\n \n\n```json\n\"scopes\": [\n \"marketing_campaigns.create\",\n \"marketing_campaigns.delete\",\n \"marketing_campaigns.read\",\n \"marketing_campaigns.update\"\n]\n```\n\n \n\n Newsletter\n \n\n```json\n\"scopes\": [\n \"newsletter.create\",\n \"newsletter.delete\",\n \"newsletter.read\",\n \"newsletter.update\"\n]\n```\n\n \n\n Partner-Settings\n \n\n```json\n\"scopes\": [\n \"partner_settings.new_relic.read\",\n \"partner_settings.new_relic.update\",\n \"partner_settings.read\",\n \"partner_settings.sendwithus.read\",\n \"partner_settings.sendwithus.update\"\n]\n```\n\na>\n\n Scheduled Sends\n \n\n```json\n\"scopes\": [\n \"user.scheduled_sends.create\",\n \"user.scheduled_sends.delete\",\n \"user.scheduled_sends.read\",\n \"user.scheduled_sends.update\"\n]\n```\n\n \n\n Stats\n \n\n```json\n\"scopes\": [\n \"email_activity.read\",\n \"stats.read\",\n \"stats.global.read\",\n \"browsers.stats.read\",\n \"devices.stats.read\",\n \"geo.stats.read\",\n \"mailbox_providers.stats.read\",\n \"clients.desktop.stats.read\",\n \"clients.phone.stats.read\",\n \"clients.stats.read\",\n \"clients.tablet.stats.read\",\n \"clients.webmail.stats.read\"\n]\n```\n\n \n\n Subusers\n \n\n```json\n\"scopes\": [\n \"subusers.create\",\n \"subusers.delete\",\n \"subusers.read\",\n \"subusers.update\",\n \"subusers.credits.create\",\n \"subusers.credits.delete\",\n \"subusers.credits.read\",\n \"subusers.credits.update\",\n \"subusers.credits.remaining.create\",\n \"subusers.credits.remaining.delete\",\n \"subusers.credits.remaining.read\",\n \"subusers.credits.remaining.update\",\n \"subusers.monitor.create\",\n \"subusers.monitor.delete\",\n \"subusers.monitor.read\",\n \"subusers.monitor.update\",\n \"subusers.reputations.read\",\n \"subusers.stats.read\",\n \"subusers.stats.monthly.read\",\n \"subusers.stats.sums.read\"\n \"subusers.summary.read\"\n]\n```\n\n \n\n Suppressions\n \n\n```json\n\"scopes\": [\n \"suppression.create\",\n \"suppression.delete\",\n \"suppression.read\",\n \"suppression.update\",\n \"suppression.bounces.create\",\n \"suppression.bounces.read\",\n \"suppression.bounces.update\",\n \"suppression.bounces.delete\",\n \"suppression.blocks.create\",\n \"suppression.blocks.read\",\n \"suppression.blocks.update\",\n \"suppression.blocks.delete\",\n \"suppression.invalid_emails.create\",\n \"suppression.invalid_emails.read\",\n \"suppression.invalid_emails.update\",\n \"suppression.invalid_emails.delete\",\n \"suppression.spam_reports.create\",\n \"suppression.spam_reports.read\",\n \"suppression.spam_reports.update\",\n \"suppression.spam_reports.delete\",\n \"suppression.unsubscribes.create\",\n \"suppression.unsubscribes.read\",\n \"suppression.unsubscribes.update\",\n \"suppression.unsubscribes.delete\"\n]\n```\n\n \n\n Teammates\n \n\n```json\n\"scopes\": [\n \"teammates.create\",\n \"teammates.read\",\n \"teammates.update\",\n \"teammates.delete\"\n]\n```\n\n \n\n Templates\n \n\n```json\n\"scopes\": [\n \"templates.create\",\n \"templates.delete\",\n \"templates.read\",\n \"templates.update\",\n \"templates.versions.activate.create\",\n \"templates.versions.activate.delete\",\n \"templates.versions.activate.read\",\n \"templates.versions.activate.update\",\n \"templates.versions.create\",\n \"templates.versions.delete\",\n \"templates.versions.read\",\n \"templates.versions.update\"\n]\n```\n\n \n\n Tracking\n \n\n```json\n\"scopes\": [\n \"tracking_settings.click.read\",\n \"tracking_settings.click.update\",\n \"tracking_settings.google_analytics.read\",\n \"tracking_settings.google_analytics.update\",\n \"tracking_settings.open.read\",\n \"tracking_settings.open.update\",\n \"tracking_settings.read\",\n \"tracking_settings.subscription.read\",\n \"tracking_settings.subscription.update\"\n]\n```\n\n \n\n User Settings\n \n\n```json\n\"scopes\": [\n \"user.account.read\",\n \"user.credits.read\",\n \"user.email.create\",\n \"user.email.delete\",\n \"user.email.read\",\n \"user.email.update\",\n \"user.multifactor_authentication.create\",\n \"user.multifactor_authentication.delete\",\n \"user.multifactor_authentication.read\",\n \"user.multifactor_authentication.update\",\n \"user.password.read\",\n \"user.password.update\",\n \"user.profile.read\",\n \"user.profile.update\",\n \"user.settings.enforced_tls.read\",\n \"user.settings.enforced_tls.update\",\n \"user.timezone.read\",\n \"user.timezone.update\",\n \"user.username.read\",\n \"user.username.update\"\n]\n```\n\n \n\n Webhook\n \n\n```json\n\"scopes\": [\n \"user.webhooks.event.settings.read\",\n \"user.webhooks.event.settings.update\",\n \"user.webhooks.event.test.create\",\n \"user.webhooks.event.test.read\",\n \"user.webhooks.event.test.update\",\n \"user.webhooks.parse.settings.create\",\n \"user.webhooks.parse.settings.delete\",\n \"user.webhooks.parse.settings.read\",\n \"user.webhooks.parse.settings.update\",\n \"user.webhooks.parse.stats.read\"\n]\n```\n\n \n\n Domain Authentication (formerly Whitelabel)\n \n\n```json\n\"scopes\": [\n \"whitelabel.create\",\n \"whitelabel.delete\",\n \"whitelabel.read\",\n \"whitelabel.update\"\n]\n```\n\n \n\n Reverse DNS (formerly Whitelist)\n \n\n```json\n\"scopes\": [\n \"access_settings.activity.read\",\n \"access_settings.whitelist.create\",\n \"access_settings.whitelist.delete\",\n \"access_settings.whitelist.read\",\n \"access_settings.whitelist.update\"\n]\n```",
"id": "api-key-permissions",
"name": "API Key Permissions",
"public": true
},
"api-rate-limits": {
"content": "## Rate Limit Response Header\n\nAll calls within the Web API are allotted a specific number of requests per refresh period.\n\nEach Web API request returns the following header information regarding rate limits and number of requests left.\n\nDepending on the endpoint you are trying to reach, it will have a specific number of allowed requests per refresh period. Once this threshold has been reached, we will return a status code `429` response.\n\n### Example\n\n```bash\nGET https://api.sendgrid.com/v3/resource HTTP/1.1\n```\n\n```json\nHTTP/1.1 200 OK\nContent-Type: application/json\nX-RateLimit-Limit: 500\nX-RateLimit-Remaining: 499\nX-RateLimit-Reset: 1392815263\n\n{\n \"foo\": \"bar\"\n}\n```\n\n## When You Reach a Rate Limit\n\nYou will no longer be able to make request against that endpoint for the duration of that refresh period.\n\n### Example\n\n```bash\nGET https://api.sendgrid.com/v3/resource/ HTTP/1.1\n```\n\n```json\nHTTP/1.1 429 TOO MANY REQUESTS\nContent-Type: application/json\nX-RateLimit-Limit: 150\nX-RateLimit-Remaining: 0\nX-RateLimit-Reset: 1392815263\n\n{\n \"errors\": [\n {\n \"field\": null,\n \"message\": \"too many requests\"\n },\n ]\n}\n```",
"id": "api-rate-limits",
"name": "Rate Limits",
"public": true
},
"api-requests": {
"content": "## Making a Request\n\n### Host\n\nThe host for Web API v3 requests is always `https://sendgrid.com/v3/`\n\n**All requests must be made over HTTPS. The API does not support HTTP.**\n\n### Authorization Header\n\nYou must provide an authorization header as described in [Authentication]().\n\n### HTTP Verbs\n\n| **Verb** | **Description** |\n| --- | --- |\n| GET | Retrieve a resource or group of resources |\n| POST| Create a new resource |\n| PUT | Update an existing resource |\n| DELETE | Delete an existing resource |\n| OPTIONS | View allowed verbs against a specific resource |\n\n### Accept Header\n\nThe API provides JSON responses. It doesn't currently require the [accept header](http://www.soapui.org/Best-Practices/understanding-rest-headers-and-parameters.html), but might in the future. If not set, the API will use `application/json`.\n\n```bash\nGET https://api.sendgrid.com/v3/endpoint HTTP/1.1\nAccept: application/json\n```\n\n### Arrays of Data\n\nWhen you send an array of data in a `GET` request, you will include the parameter multiple times on the URL. The parameter name does not require brackets.\n\n#### Example Array in a GET request\\\n\n```bash\nGET https://api.sendgrid.com/v3/endpoint?parameter=data1¶meter=data2 HTTP/1.1\n```\n\n## Formatting Your Request\n\n### Request Body\n\nWhen submitting data to a resource via `POST` or `PUT`, you must submit your payload in JSON.\n\n```bash\nPOST https://api.sendgrid.com/v3/templates/ HTTP/1.1\nContent-Type: application/json\n```\n\n```json\n{\n \"name\": \"new template name\"\n}\n```\n\n### Pagination\n\nSome `GET` resources allow for retrieval of information in batches. We will provide the query args in the resource documentation when available to consume.\n\nWhen requesting multiple items, we will default the request limit to 500 items. You can specify a different limit but cannot exceed the default limit.\n\nResources documented will display a bolded list of available paginated parameters if available.\n\nBelow is a basic pagination example. In the resource documentation, we will only provide the bolded list of available parameters.\n\n**A [Link Header](http://tools.ietf.org/html/rfc5988) is provided in the response for batched information.**\n\n```bash\nGET https://api.sendgrid.com/v3/resource?limit=300&offset=10 HTTP/1.1\n```\n\n| **Parameter** | **Description** |\n|---|---|\n| limit | The number of records to return |\n| offset | The number of records to skip |\n\n### Search & Paramters\n\nSome resources allow for you to search by a specific field. Other resources require you to append a parameter to the URI.\n\nIn this example, we will display a paginated URI example, searching for resources where the email contains `foo`.\n\n```bash\nGET https://api.sendgrid.com/v3/resource?email=foo&bar=baz HTTP/1.1\n```\n\n## Successful Requests\n\nBelow is a general overview of what resource objects return with successful Web API requests.\n\n| **Verb** | **Resource object returned** |\n|---|---|\n| GET | Returns a single resource object or array of resource objects |\n| PATCH | Returns the updated resource object |\n| PUT | Returns the updated resource object |\n| DELETE | No content is returned |\n| POST | Returns the newly created resource object |",
"id": "api-requests",
"name": "Requests",
"public": true
},
"api-responses": {
"content": "## Content-Type Header\n\nAll responses are returned in JSON format. We specify this by sending the `Content-Type` header.\n\n```bash\nGET https://api.sendgrid.com/v3/resource HTTP/1.1\n```\n\n```json\nHTTP/1.1 200 OK\nContent-Type: application/json\n\n{\n \"foo\": \"bar\"\n}\n```\n\n## Status Codes\n\nBelow is a table containing descriptions of the various status codes we currently support against various resources.\n\n| **Status Code** | **Description** |\n|---|---|\n| 200 | No error |\n| 201 | Successfully created |\n| 204 | Successfully deleted |\n| 400 | Bad request|\n| 401 | Requires authentication |\n| 403 | From address doesn't match Verified Sender Identity. To learn how to resolve this error, see our [Sender Identity requirements]({{root_url}}for-developers/sending-email/sender-identity/). |\n| 406 | Missing Accept header. For example: `Accept: application/json` |\n| 429 | Too many requests/Rate limit exceeded |\n| 500 | Internal server error |\n\n\n## Pagination\n\nWhen a request is made with a pagination query, the following data is included in the header to allow for easy traversal of previous, current, first, and last page of the data set.\n\n```bash\nGET https://api.sendgrid.com/v3/resource?limit=5&offset=0 HTTP/1.1\n```\n\n```json\nHTTP/1.1 200 OK\nContent-Type: application/json\n\nLink: ; rel=\"next\"; title=\"2\",\n ; rel=\"prev\"; title=\"1\",\n ; rel=\"last\"; title=\"3\",\n ; rel=\"first\"; title=\"1\"\n\n```",
"id": "api-responses",
"name": "Responses",
"public": true
},
"mail-send-errors": {
"content": "\n\n\n Personalizations Errors \n\n\n\n\n personalizations \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The personalization block is limited to 1000 personalizations per API request. You have provided X personalizations. Please consider splitting this into multiple requests and resending your request. \n \n \n\n \n The v3 Mail Send endpoint is only capable of processing 1000 individual personalizations
objects per request. If you need to send your email to more than 1000 different recipients, we recommend that you simply make more than one call. For more information about the personalizations
array, and how you can use it to define who you would like you send your email to, and how you would like your email to be processed, please visit our Personalizations documentation . \n \n\n\n \n You must have at least one personalization. \n \n \n\n \n Every email you send must have at least one recipient email address included. Recipients are defined in the personalizations
array. For more information on how to use personalizations
to define who you want to send your email to, please visit our Personalizations documentation . \n \n\n\n\n There is a limit of 1000 total recipients (to + cc + bcc) per request. \n \n \n\n \n The combined, total number of email recipients may never exceed 1000. This includes recipients defined within the to
, cc
, and bcc
parameters. For more information on how you may specify your recipients, please visit our Personalizations documentation . \n \n\n\n \n Each \"to\" object must at least have an email address and may also contain a name. e.g. {\"email\": \"example@example.com\"}
or {\"email\": \"example@example.com\", \"name\": \"Example Recipient\"}
\n \n \n\n \n For every recipient that you define within a personalizations.to
parameter, you must include one valid email address. You are not required to include a name. \n \n\n\n \n Each unique email address in the personalizations
array should only be included once. You have included [email address] more than once. \n \n \n\n \n To prevent the same email from being delivered to one recipient multiple times, SendGrid will confirm that you do not duplicate an email address in your request. For more information on how you may specify your recipients, please visit our Personalizations documentation . \n \n\n\n \n The to parameter is required for all personalization objects. \n \n \n\n \n For every single object that you include within the personalizations
array, you must include at least one to
email object with a valid email address. For more information on how you may specify your recipients, please visit our Personalizations documentation . \n \n \n
\n\n\n\n\n personalizations.bcc \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The bcc array, when used, must at least have an email
parameter with a valid email address and it may also contain a name
parameter. e.g. {\"email\": \"example@example.com\"}
or {\"email\": \"example@example.com\", \"name\": \"Example Recipient\"}
\n \n \n\n \n For every blind carbon copy of your email, you must include one, valid recipient email address. However, you are not required to include a corresponding name with the recipient email address. For more information on how to use personalizations
to define who you want to send your email to, please visit our Personalizations documentation . \n \n\n\n \n Each recipient object must at least have an email address and may also contain a name. e.g. {\"email\": \"example@example.com\"}
or {\"email\": \"example@example.com\", \"name\": \"Example Recipient\"}
\n \n \n\n \n Every recipient that you define within the personalizations.cc
array must be in the form of an email object including one, valid email address. You are not required to include a name. \n \n\n\n \n Each unique email address in the personalization block should only be included once. You have included [email address] more than once. \n \n \n\n \n To prevent the same email from being delivered to one recipient multiple times, SendGrid will confirm that you do not duplicate an email address in your request. For more information on how you may specify your recipients, please visit our Personalizations documentation . \n \n\n\n \n
\n\n\n\n\n personalizations.cc \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The cc array, when used, must at least have an email
parameter with a valid email address and it may also contain a name
parameter. e.g. {\"email\": \"example@example.com\"}
or {\"email\": \"example@example.com\", \"name\": \"Example Recipient\"}
\n \n \n\n \n For every carbon copy of your email, you must include one valid recipient email address. However, you are not required to include a corresponding name with the recipient email address. For more information on how to use personalizations
to define who you want to send your email to, please visit our Personalizations documentation . \n \n\n\n \n Each recipient object must at least have an email address and may also contain a name. e.g. {\"email\": \"example@example.com\"}
or {\"email\": \"example@example.com\", \"name\": \"Example Recipient\"}
\n \n \n\n \n Every recipient that you define within the personalizations.cc
array must be in the form of an email object including one, valid email address. You are not required to include a name. \n \n\n\n \n Each unique email address in the personalization block should only be included once. You have included [email address] more than once. \n \n \n\n \n To prevent the same email from being delivered to one recipient multiple times, SendGrid will confirm that you do not duplicate an email address in your request. For more information on how you may specify your recipients, please visit our Personalizations documentation . \n \n\n\n \n
\n\n\n\n\n personalizations.custom_args \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n All values of custom arguments object must be strings \n \n \n\n \n Custom argument values must always be strings. You cannot define arrays, integers, or booleans as custom argument values. Click here more information about how to use custom arguments. \n \n\n\n \n custom_args
cannot be empty. \n \n \n\n \n If you include the custom_args
parameter, you must include at least one value. If you do not want to use any custom arguments, simply omit the custom_arg
param from your request. Click here more information about how to use custom arguments. \n \n\n\n \n The combined size of both global and personalization custom arguments may not exceed 10,000 bytes per personalization. \n \n \n\n \n personalizations[x].custom_args
will be merged with message level custom_args
, overriding any conflicting keys. The combined total size of the resulting custom arguments, after merging, for each personalization may not exceed 10,000 bytes. Click here more information about how to use custom arguments. \n \n\n\n \n
\n\n\n\n\n personalizations.headers \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n All values of the headers object must be strings. \n \n \n\n \n The object type of every header that you include must be a string. You cannot include headers that are integers, booleans, or arrays. \n \n\n\n \n The headers cannot contain duplicate keys. \n \n \n\n \n You may not include the same header more than once. \n \n\n\n \n Header keys cannot contain non-ASCII characters or empty spaces. \n \n \n\n \n When defining the headers that you would like to use, you must make sure that the string contains only ASCII characters. \n \n\n\n \n Header cannot be one of the reserved keys. \n \n \n\n \n Some header keys are reserved. You may not include any of the following reserved keys: x-sg-id
, x-sg-eid
, received
, dkim-signature
, Content-Type
, Content-Transfer-Encoding
, To
, From
, Subject
, Reply-To
, CC
, BCC
. \n \n\n\n \n
\n\n\n\n\n personalizations.send_at \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The send_at
parameter is expecting a Unix timestamp as an integer. We will send immediately if you include a send_at
timestamp that is in the past. \n \n \n\n \n send_at
accepts a unix timestamp representing when you want your email to be sent from SendGrid. If you want the email to be sent at the time of your API request, simply omit the send_at
parameter. \n \n\n\n \n Scheduling more than 72 hours in advance is forbidden. \n \n \n\n \n The send_at
parameter allows you to schedule your email to be sent up to 72 hours in advance, but due to our constraints, we cannot schedule emails more than 72 hours in the future. For more information on scheduling parameters, please see our API Reference . \n \n\n\n \n
\n\n\n\n\n personalizations.subject \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The subject of your email must be a string at least one character in length. \n \n \n\n \n You are required to include a subject for every email you send, and you may not include an empty subject. The minimum length of your subject is one character. \n \n\n\n \n The subject is required. You can get around this requirement if you use a template with a subject defined. \n \n \n\n \n Every email must have a subject. This can be accomplished 3 ways: you include a template that has a subject, you define a global subject, or every single personalizations object has a subject. \n \n\n\n \n
\n\n\n\n\n personalizations.substitutions \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The substitution values must be an object of key/value pairs, where the values are all strings. \n \n \n\n \n Substitutions must always follow the pattern \"substitution_tag\": \"value to substitute\"
. The value to substitute for the \"substitution_tag\" must always be a string. \n \n\n\n \n You are limited to 10,000 substitutions. \n \n \n\n \n You may not make more than 10,000 bytes worth of substitutions per request. Substitutions will apply to the content of your email, in addition to the subject
and reply_to
parameters \n \n\n\n \n
\n\n\n\n\n personalizations.to \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The to array must at least have an email
parameter with a valid email address and it may also contain a name
parameter. e.g. {\"email\": \"example@example.com\"}
or {\"email\": \"example@example.com\", \"name\": \"Example Recipient\"}
\n \n \n\n \n Every email you send must have at least one, valid recipient email address included. However, you are not required to include a corresponding name with the recipient email address. For more information on how to use personalizations
to define who you want to send your email to, please visit our Personalizations documentation . \n \n\n\n \n
\n\n\n\n\n ASM Errors \n\n\n\n\n asm.group_id \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The ASM group ID must be an integer. \n \n \n\n \n Suppression Groups, or unsubscribe groups, are a great way to record which emails your recipients want to receive. Including the asm.group_id
in your request allows you to group your email with other, similar sends. However, these group IDs are always generated as integers, and so you must ensure that asm.group_id
is defined as an integer. For more information please read our Unsubscribe Groups documentation. \n \n\n\n \n The ASM group ID must be a valid Group ID on your account. You provided [YOUR ASM GROUP ID]. \n \n \n\n \n \n \n\n\n \n
\n\n\n\n\n asm.groups_to_display \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The ASM Group IDs to display must be an array of integers. \n \n \n\n \n \n \n\n\n \n All ASM groups to display must be valid ASM groups IDs on your account. You provided {invalid IDs}. \n \n \n\n \n \n \n\n\n \n There is a limit of 25 unsubscribe groups that can be displayed to a user at a time. \n \n \n\n \n \n \n\n\n \n
\n\n\n\n\n Attachment Errors \n\n\n\n\n attachments.content \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The attachment content must be base64 encoded. \n \n \n\n \n \n \n\n\n \n The attachment content must be a string at least one character in length. \n \n \n\n \n \n \n\n\n \n
\n\n\n\n\n attachments.content_id \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The content ID of your attachment must be a string. You provided [YOUR CONTENT ID]. \n \n \n\n \n The content_id
is a unique id that you specify for the attachment. This is used when the disposition is set to \"inline\" and the attachment is an image, allowing the file to be displayed within the body of your email. For example, <img src=\"cid:ii_139db99fdb5c3704\"></img>
\n \n\n\n \n The content ID of your attachment cannot contain CRLF characters. \n \n \n\n \n When defining your content_id
, you may not include the characters ;
, ,
, n
, or r
. \n \n\n\n \n
\n\n\n\n\n attachments.disposition \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The disposition of your attachment can be either \"inline\" or \"attachment\". You provided [YOUR DISPOSITION]. \n \n \n\n \n The content-disposition of your attachment defines how you would like the attachment to be displayed. For example, \"inline\" results in the attached file being displayed automatically within the message while \"attachment\" results in the attached file requiring some action to be taken before it is displayed (e.g. opening or downloading the file). attachments.disposition
always defaults to \"attachment\". Can be either \"attachment\" or \"inline\". \n \n\n\n \n
\n\n\n\n\n attachments.filename \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The filename of your attachment must be a string. \n \n \n\n \n \n \n\n\n \n The filename of your attachment cannot contain CRLF characters. \n \n \n\n \n When defining the filename of your attachment, you may not include the characters ;
, ,
, n
, or r
. \n \n\n\n \n filename is required. \n \n \n\n \n You must always include a filename for your attachment. \n \n\n\n \n
\n\n\n\n\n attachments.type \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The attachment type must be a string and at least one character in length. \n \n \n\n \n \n \n\n\n \n The type cannot contain ‘;’, or CRLF characters. \n \n \n\n \n When defining the type of your attachment content, you may not include the characters ;
, ,
, n
, or r
. \n \n\n\n \n
\n\n\n\n\n Batch ID Errors \n\n\n\n\n batch_id \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The batch_id must be a string. \n \n \n\n \n Batch IDs are always generated as strings, so when you choose to include a batch ID in your send, you must make sure that batch_id
is defined as a string. You must first generate a batch_id
via the API; it will not be automatically generated for you. See the Create a Batch ID API reference to generate a batch_id
. For more information about batch IDs, and how you can use them to group sends together, please visit our API Reference . \n \n\n\n \n
\n\n\n\n\n Categories Errors \n\n\n\n\n categories \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n There is a limit of 10 categories for each email that is sent. You provided X categories. \n \n \n\n \n For more information on how you can use categories to organize your email analytics, please visit our Categories documentation . \n \n\n\n \n Categories must be an array of strings. \n \n \n\n \n Every object that you include within the categories array must be a string. For more information on how you can use categories to organize your email analytics, please visit our Categories documentation . \n \n\n\n \n Each category must not be longer than 255 characters. [YOUR CATEGORY] exceeds this limit \n \n \n\n \n he maximum length of a category is 255 characters. For more information on how you can use categories to organize your email analytics, please visit our Categories documentation . \n \n\n\n \n The categories must be a unique list, and you have included [YOUR CATEGORY] more than once. \n \n \n\n \n You cannot include the same category more than once within the categories array. For more information on how you can use categories to organize your email analytics, please visit our Categories documentation . \n \n\n\n \n Categories can not contain non-ASCII characters. \n \n \n\n \n Each category name must only be comprised of ASCII characters. For more information on how you can use categories to organize your email analytics, please visit our Categories documentation . \n \n\n\n \n
\n\n\n\n\n Content Errors \n\n\n\n\n content \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The content param is required unless you are using a transactional template and have defined a template_ID. \n \n \n\n \n You may not send an email without the content parameter unless you are using a transactional template. This is to prevent you from sending an empty email to your recipients. \n \n\n\n \n There must be at least one defined content block. We typically suggest both text/plain and text/html blocks are included, but only one block is required. \n \n \n\n \n You must specify at least one content type and value for every email you send. We recommend that you include both text/plain and text/html to ensure that all of your recipients are able to read your email, but you are only required to define one type of content. \n \n\n\n \n
\n\n\n\n\n content.type \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n A content type is required, this tells email clients how to display the email. \n \n \n\n \n You must always specify the MIME type of content you are including in your email, and if you are including the text/plain type, it must be specified first, followed by text/html, and then any other MIME types you would like to specify. \n \n\n\n \n The content value must be a string at least one character in length. \n \n \n\n \n You may not send an email with no content. \n \n\n\n \n Your content type must be a string with length of at least one character. \n \n \n\n \n The content of your email must always be contained within a string when you make your request. \n \n\n\n \n Cannot contain ‘;’, or CRLF characters. \n \n \n\n \n When defining the type of your content, you may not include the characters ;
, ,
, n
, or r
. \n \n\n\n \n
\n\n\n\n\n content.value \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n A content value is required, this is the content of the email you are sending. \n \n \n\n \n We do not allow you to send an empty email to your recipients. You must always include a value for your content. \n \n\n\n \n The content value must be a string at least one character in length. \n \n \n\n \n We do not allow you to send an empty email to your recipients. Even if you include the content.value
parameter, it must include at least one character. \n \n\n\n \n Following RFC 1341, section 7.2, if either text/html or text/plain are to be sent in your email: text/plain needs to be first, followed by text/html, followed by any other content. \n \n \n\n \n The order in which you specify the MIME types of your content must always be text/plain first, if you choose to include it, followed by text/html, and then any other MIME types you wish to include. \n \n\n\n \n
\n\n\n\n\n Encoding Errors \n\n\n\n\n Encoding \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 415 \n \n \n \n\n\n \n Invalid UTF8 in request \n \n \n\n \n Your payload must be encoded in UTF-8. This includes any attachments. \n \n\n\n \n
\n\n\n\n\n From Address Errors \n\n\n\n\n from \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The from object must at least have an email
parameter with a valid email address and may also contain a name
parameter. e.g. {\"email\": \"example@example.com\"}
or {\"email\": \"example@example.com\", \"name\": \"Example Recipient\"}
\n \n \n\n \n While every from
parameter must include a valid email address, you are not required to include a name. \n \n\n\n \n The from
object must be provided for every email send. It is an object that requires the email
parameter, but may also contain a name
parameter. e.g. {\"email\": \"example@example.com\"}
or {\"email\": \"example@example.com\", \"name\": \"Example Recipient\"}
\n \n \n\n \n You are required to provide a from address whenever you send an email through SendGrid. This is used for authentication purposes and helps to build a positive sending reputation with your recipients' ISPs. You are not required to include a name within the from
parameter. \n \n\n\n \n
\n\n\n\n\n Headers Errors \n\n\n\n\n headers \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The header values must be strings. \n \n \n\n \n The object type of every header that you include must be a string. You cannot include headers that are integers, booleans, or arrays. \n \n\n\n \n The headers cannot contain duplicate keys. \n \n \n\n \n You may not include the same header more than once. \n \n\n\n \n Header keys cannot contain non-ASCII characters and empty spaces. \n \n \n\n \n When defining the headers that you would like to use, you must make sure that the string contains only ASCII characters. \n \n\n\n \n Header can not be one of the reserved keys. \n \n \n\n \n Some header keys are reserved. You may not include any of the following reserved headers: x-sg-id
, x-sg-eid
, received
, dkim-signature
, Content-Type
, Content-Transfer-Encoding
, To
, From
, Subject
, Reply-To
, CC
, BCC
. \n \n\n\n \n
\n\n\n\n\n IP Pool Errors \n\n\n\n\n ip_pool_name \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The name of your IP pool must be a string. \n \n \n\n \n \n \n\n\n \n The IP Pool name must be a valid pool name for your account. You provided [YOUR IP POOL NAME]. \n \n \n\n \n \n \n\n\n \n
\n\n\n\n\n Reply To Errors \n\n\n\n\n reply_to \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The reply_to
object, when used, must at least have an email
parameter with a valid email address and it may also contain a name
parameter. e.g. {\"email\": \"example@example.com\"}
or {\"email\": \"example@example.com\", \"name\": \"Example Recipient\"}
\n \n \n\n \n For every carbon copy of your email, you must include one, valid recipient email address. However, you are not required to include a corresponding name with the recipient email address. For more information on how to use personalizations
to define who you want to send your email to, please visit our Personalizations documentation . \n \n\n\n \n
\n\n\n\n\n Sections Errors \n\n\n\n\n sections \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The section values must be strings. \n \n \n\n \n The values of your sections
parameter will be substituted into the content of your email. We will only perform substitutions using strings, so please make sure that your section values are always defined as strings. \n \n\n\n \n
\n\n\n\n\n Send At Errors \n\n\n\n\n send_at \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The send_at
parameter is expecting a Unix timestamp as an integer. We will send immediately if you include a send_at
timestamp that is in the past. \n \n \n\n \n send_at
accepts a unix timestamp representing when you want your email to be sent from SendGrid. If you want the email to be sent at the time of your API request, simply omit the send_at
parameter. \n \n\n\n \n Scheduling more than 72 hours in advance is forbidden. \n \n \n\n \n The send_at
parameter allows you to schedule your email to be sent up to 72 hours in advance, but due to our constraints, we cannot schedule emails more than 72 hours in the future. For more information on scheduling parameters, please see our API Reference . \n \n\n\n \n
\n\n\n\n\n Subject Line Errors \n\n\n\n\n subject \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The subject of your email must be a string at least one character in length. \n \n \n\n \n You are required to include a subject for every email you send, and you may not include an empty subject. The minimum length of your subject is one character. \n \n\n\n \n The subject is required. You can get around this requirement if you use a template with a subject defined or if every personalization has a subject defined. \n \n \n\n \n Every email must have a subject. This can be accomplished 3 ways: you include a template that has a subject, you define a global subject, or every single personalizations object has a subject. \n \n\n\n \n
\n\n\n\n\n Template Errors \n\n\n\n\n template_id \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The template ID must be a string, you provided [YOUR TEMPLATE ID]. \n \n \n\n \n Template IDs are always strings. \n \n\n\n \n The Template ID must be a valid template id for your account. You provided [YOUR TEMPLATE ID]. \n \n \n\n \n We do not allow you to send an empty email, so in the event that the only content of your email is contained within your template, we want to make sure that the included template exists, and is valid. \n \n\n\n \n Must be a valid template. \n \n \n\n \n We do not allow you to send an empty email, so in the event that the only content of your email is contained within your template, we want to make sure that the included template exists, and is valid. \n \n\n\n \n
\n\n\n\n\n Mail Settings Errors \n\n\n\n\n mail_settings.bcc.email \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The bcc email recipient object must at least have an email address and may also contain a name. e.g. {\"email\": \"example@example.com\"}
or {\"email\": \"example@example.com\", \"name\": \"Example Recipient\"}
\n \n \n\n \n \n \n\n\n \n You must include a recipient object when using the bcc mail setting. \n \n \n\n \n \n \n\n\n \n
\n\n\n\n\n mail_settings.bcc.enable \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The bcc enable param should be a boolean value. \n \n \n\n \n \n \n\n\n \n
\n\n\n\n\n mail_settings.bypass_list_management.enable \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The bypass list management enable param should be a boolean value. \n \n \n\n \n \n \n\n\n \n
\n\n\n\n\n mail_settings.footer.enable \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The footer enable param should be a boolean value. \n \n \n\n \n \n \n\n\n \n
\n\n\n\n\n mail_settings.footer.html \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The text/html version of your footer should be a string. \n \n \n\n \n \n \n\n\n \n
\n\n\n\n\n mail_settings.footer.text \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The text/plain version of your footer should be a string. \n \n \n\n \n \n \n\n\n \n
\n\n\n\n\n mail_settings.sandbox_mode.enable \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The sandbox mode enable param should be a boolean value. \n \n \n\n \n \n \n\n\n \n
\n\n\n\n\n mail_settings.spam_check.enable \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The spam check enable param should be a boolean value. \n \n \n\n \n \n \n\n\n \n
\n\n\n\n\n mail_settings.spam_check.post_to_url \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The spam check url must be a string. \n \n \n\n \n \n \n\n\n \n You must include the url to post to when using the spam check mail setting. \n \n \n\n \n \n \n\n\n \n The `post_to_url` parameter must start with `http://` or `https://`. \n \n \n\n \n \n \n\n\n \n
\n\n\n\n\n mail_settings.spam_check.threshold \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The spam check threshold is between 1 and 10, with the lower numbers being the most strict filtering. \n \n \n\n \n \n \n\n\n \n You must include the spam check threshold when using the spam check mail setting. \n \n \n\n \n \n \n\n\n \n
\n\n\n\n\n Tracking Settings Errors \n\n\n\n\n tracking_settings.click_tracking.enable \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The click tracking enable param should be a boolean value. \n \n \n\n \n \n \n\n\n \n
\n\n\n\n\n tracking_settings.click_tracking.enable_text \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The click tracking enable text must be a boolean value. \n \n \n\n \n \n \n\n\n \n
\n\n\n\n\n tracking_settings.ganalytics.enable \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The Google Analytics enable param must be a boolean value. \n \n \n\n \n \n \n\n\n \n
\n\n\n\n\n tracking_settings.ganalytics.utm_campaign \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The Google Analytics utm_campaign must be a string value. \n \n \n\n \n \n \n\n\n \n
\n\n\n\n\n tracking_settings.ganalytics.utm_content \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The Google Analytics utm_content must be a string value. \n \n \n\n \n \n \n\n\n \n
\n\n\n\n\n tracking_settings.ganalytics.utm_medium \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The Google Analytics utm_medium must be a string value. \n \n \n\n \n \n \n\n\n \n
\n\n\n\n\n tracking_settings.ganalytics.utm_source \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The Google Analytics utm_source must be a string value. \n \n \n\n \n \n \n\n\n \n
\n\n\n\n\n tracking_settings.ganalytics.utm_term \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The Google Analytics utm_term must be a string value. \n \n \n\n \n \n \n\n\n \n
\n\n\n\n\n tracking_settings.open_tracking.enable \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The open tracking enable param should be a boolean value. \n \n \n\n \n \n \n\n\n \n
\n\n\n\n\n tracking_settings.open_tracking.substitution_tag \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The open tracking substitution tag must be a string. \n \n \n\n \n \n \n\n\n \n
\n\n\n\n\n tracking_settings.subscription_tracking.enable \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The subscription tracking enable param should be a boolean value. \n \n \n\n \n \n \n\n\n \n
\n\n\n\n\n tracking_settings.subscription_tracking.html \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The subscription tracking unsubscribe content for text/html messages must be a string. \n \n \n\n \n \n \n\n\n \n
\n\n\n\n\n tracking_settings.subscription_tracking.substitution_tag \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The subscription tracking substitution tag must be a string value. \n \n \n\n \n \n \n\n\n \n
\n\n\n\n\n tracking_settings.subscription_tracking.text \n\n\n\n\n\n \n \n Error Code \n \n Details \n \n \n \n \n 400 \n \n \n \n\n\n \n The subscription tracking unsubscribe content for text/plain messages must be a string. \n \n \n\n \n \n \n\n\n \n
\n\n\n\n\n\n ",
"id": "mail-send-errors",
"name": "Errors",
"public": true
},
"mail-send-limitations": {
"content": "There are several rate limitations and restrictions that you should be aware of when using the v3 Mail Send endpoint.\n\n* The total size of your email, including attachments, must be less than 30MB.\n* The total number of recipients must no more than 1000. This includes all recipients defined within the `to`, `cc`, and `bcc` parameters, across each object that you include in the `personalizations` array.\n* The total length of custom arguments must be less than 10000 bytes.\n* Unicode encoding is not supported for the `from` field.\n* The `to.name`, `cc.name`, and `bcc.name` personalizations cannot include either the `;` or `,` characters.\n\nFor more specific, parameter level requirements and limitations, please refer to the [v3/mail/send documentation](https://sendgrid.api-docs.io/v3.0/mail-send/v3-mail-send).",
"id": "mail-send-limitations",
"name": "Limitations",
"public": true
},
"mail-send-validation": {
"content": "Whenever you make a request to the v3 Mail Send endpoint, your JSON payload is validated before your email is sent. If there are any errors, SendGrid will attempt to identify and return as many issues as possible for each request. For more information, please read our [error documentation](https://sendgrid.api-docs.io/v3.0/mail-send/mail-send-errors).",
"id": "mail-send-validation",
"name": "Validation",
"public": true
},
"on-behalf-of": {
"content": "Use the *on-behalf-of* header to make calls for a particular subuser through the parent account. Use this header to automate bulk updates, or to administer a subuser without changing the authentication in your code.\n\nTo use the feature, add the *on-behalf-of* header:\n\n`on-behalf-of: << subuser username >>`\n\nThis header generates the API call as if the subuser account was making the call.\n\nWhen authenticating using the *on-behalf-of* header, you need to use the API key credentials of the **parent account**. For example:\n\n```curl --request GET \\\n --url 'https://api.sendgrid.com/v3/stats?start_date=2018-02-01&aggregated_by=day' \\\n --header 'authorization: Bearer << API KEY >>' \\\n --header 'on-behalf-of: << subuser username >>'\n ```\n\n**Note**: The *on-behalf-of* header does not work with the mail.send API.",
"id": "on-behalf-of",
"name": "On Behalf of Subuser",
"public": true
},
"tip": {
"content": "Exporting your contacts regularly as a backup to avoid issues is a recommended best practice.",
"id": "tip",
"name": "Tip",
"public": true
}
},
"version": {
"groups": {
"docs": [
{
"divider": true,
"items": [],
"name": "Getting Started"
},
{
"description": "Welcome to SendGrid’s Web API v3! This API is RESTful, fully featured, easy to integrate with, and offers support in [7 different languages](https://sendgrid.com/docs/for-developers/sending-email/libraries/).\n\n## Libraries\n\n* [C#](https://github.com/sendgrid/sendgrid-csharp)\n* [Go](https://github.com/sendgrid/sendgrid-go)\n* [Java](https://github.com/sendgrid/sendgrid-java)\n* [Node.js](https://github.com/sendgrid/sendgrid-nodejs)\n* [PHP](https://github.com/sendgrid/sendgrid-php)\n* [Python](https://github.com/sendgrid/sendgrid-python)\n* [Ruby](https://github.com/sendgrid/sendgrid-ruby)",
"divider": false,
"items": [
{
"_id": "api-authentication",
"type": "docTextSections"
},
{
"_id": "api-authorization",
"type": "docTextSections"
},
{
"_id": "api-requests",
"type": "docTextSections"
},
{
"_id": "on-behalf-of",
"type": "docTextSections"
},
{
"_id": "api-responses",
"type": "docTextSections"
},
{
"_id": "api-rate-limits",
"type": "docTextSections"
},
{
"_id": "api-errors",
"type": "docTextSections"
}
],
"name": "How to use the SendGrid v3 API"
},
{
"description": "",
"divider": false,
"items": [
{
"_id": "POST_mail-send",
"type": "endpoints"
},
{
"_id": "mail-send-limitations",
"type": "docTextSections"
},
{
"_id": "mail-send-validation",
"type": "docTextSections"
},
{
"_id": "mail-send-errors",
"type": "docTextSections"
},
{
"_id": "cc_bcc_email_object",
"type": "schemas"
},
{
"_id": "from_email_object",
"type": "schemas"
},
{
"_id": "reply_to_email_object",
"type": "schemas"
},
{
"_id": "to_email_array",
"type": "schemas"
},
{
"_id": "mailSendErrors",
"type": "traits"
}
],
"name": "Mail Send"
},
{
"description": "The Cancel Scheduled Sends API allows you to cancel or pause the send of one or more emails using a batch ID.\n\nA `batch_id` groups multiple scheduled `mail/send` requests together with the same ID. You can cancel or pause all of the `mail/send` requests associated with a batch ID up to 10 minutes before the scheduled send time by passing a `batch_id` to the \"Cancel or puase a scheduled send\" endpoint.\n\nFor a guide on creating a `batch_id`, assigning it to a scheduled send, and modifying the send, see [\"Canceling a Scheduled Send\"](https://sendgrid.com/docs/for-developers/sending-email/stopping-a-scheduled-send/).\n\nThe Cancel Scheduled Sends API also make it possible to validate a `batch_id` and retrieve all scheduled sends as an array.\n\nWhen a batch is canceled, all messages associated with that batch will stay in your sending queue. When their `send_at` value is reached, they will be discarded.\n\nWhen a batch is paused, all messages associated with that batch will stay in your sending queue, even after their `send_at` value has passed. This means you can remove a `pause` status, and your scheduled send will be delivered once the pause is removed. Any messages left with a `pause` status that are more than 72 hours old will be discarded as Expired.",
"divider": false,
"items": [
{
"_id": "POST_mail-batch",
"type": "endpoints"
},
{
"_id": "POST_user-scheduledsends",
"type": "endpoints"
},
{
"_id": "GET_mail-batch-batchid",
"type": "endpoints"
},
{
"_id": "GET_user-scheduledsends",
"type": "endpoints"
},
{
"_id": "GET_user-scheduledsends-batchid",
"type": "endpoints"
},
{
"_id": "PATCH_user-scheduledsends-batchid",
"type": "endpoints"
},
{
"_id": "DELETE_user-scheduledsends-batchid",
"type": "endpoints"
},
{
"_id": "mail_batch_id",
"type": "schemas"
},
{
"_id": "user_scheduled_send_status",
"type": "schemas"
},
{
"_id": "cancelScheduledSendsErrors",
"type": "traits"
}
],
"name": "Cancel Scheduled Sends"
},
{
"divider": true,
"items": [],
"name": "Security"
},
{
"description": "Your application, mail client, or website can all use API (Application Programming Interface) keys to authenticate access to SendGrid services. You can revoke an API key at any time without having to change your username and password, and an API key can be scoped to perform a limited number of actions.\n\nThere are 3 different types of API keys:\n\n* **Full Access** \nAllows the API key to access `GET`, `PATCH`, `PUT`, `DELETE` and `POST` endpoints for all parts of your account, excluding billing and Email Address Validation.\n* **Restricted Access** \nCustomizes levels of access for all parts of your account, excluding billing and Email Address Validation.\n* **Billing Access** \nAllows the API key to access billing endpoints for the account.\n\nYou must create your first API key using the [Twilio SendGrid App](https://app.sendgrid.com/settings/api_keys). Once you have a key with permissions to manage other keys, you can use the endpoints documented as part of this API.",
"divider": false,
"items": [
{
"_id": "create-api-keys",
"type": "endpoints"
},
{
"_id": "GET_apikeys",
"type": "endpoints"
},
{
"_id": "GET_apikeys-apikeyid",
"type": "endpoints"
},
{
"_id": "PATCH_apikeys-apikeyid",
"type": "endpoints"
},
{
"_id": "PUT_apikeys-apikeyid",
"type": "endpoints"
},
{
"_id": "DELETE_apikeys-apikeyid",
"type": "endpoints"
},
{
"_id": "api_key_name_id",
"type": "schemas"
},
{
"_id": "api_key_name_id_scopes",
"type": "schemas"
},
{
"_id": "apiKeysErrors",
"type": "traits"
}
],
"name": "API Keys"
},
{
"divider": false,
"items": [
{
"_id": "api-key-permissions",
"type": "docTextSections"
},
{
"_id": "GET_scopes",
"type": "endpoints"
}
],
"name": "API Key Permissions"
},
{
"description": "The Enforced TLS settings specify whether or not the recipient of your send is required to support TLS or have a valid certificate. The Enforced TLS endpoint supports retrieving and updating TLS settings.\n\nTwilio SendGrid sends all emails with [Opportunistic TLS](https://sendgrid.com/blog/myth-opportunistic-tls-email-privacy/) by default, meaning email is sent with TLS, and if the recipient's inbox provider does not accept the TLS encryption, we then send the message unencrypted.\n\nYou can optionally choose to enforce TLS encryption, meaning that if the recipient's inbox provider does not accept the TLS encryption, Twilio SendGrid drops the message and sends a block event with “TLS required but not supported” as the description.",
"divider": false,
"items": [
{
"_id": "GET_user-settings-enforcedtls",
"type": "endpoints"
},
{
"_id": "PATCH_user-settings-enforcedtls",
"type": "endpoints"
},
{
"_id": "enforced-tls-request-response",
"type": "schemas"
}
],
"name": "Settings - Enforced TLS"
},
{
"description": "IP Access Management allows you to control which IP addresses can be used to access your account, either through the User Interface or the API.\n\nThere is no limit to the number of IP addresses that you can allow.\n\n> It is possible to remove your own IP address from your list of allowed addresses, thus blocking your own access to your account. While we are able to restore your access, we do require thorough proof of your identify and ownership of your account. We take the security of your account very seriously, and wish to prevent any 'bad actors' from maliciously gaining access to your account.\n>\n> Your current IP is clearly displayed to help prevent you from accidentally removing it from the allowed addresses.\n\nFor more information, please see our [IP Access Management documentation](https://sendgrid.com/docs/ui/account-and-settings/ip-access-management/).",
"divider": false,
"items": [
{
"_id": "POST_accesssettings-whitelist",
"type": "endpoints"
},
{
"_id": "GET_accesssettings-activity",
"type": "endpoints"
},
{
"_id": "GET_accesssettings-whitelist",
"type": "endpoints"
},
{
"_id": "GET_accesssettings-whitelist-ruleid",
"type": "endpoints"
},
{
"_id": "DELETE_accesssettings-whitelist",
"type": "endpoints"
},
{
"_id": "DELETE_accesssettings-whitelist-ruleid",
"type": "endpoints"
},
{
"_id": "ip-access-response",
"type": "schemas"
}
],
"name": "IP Access Management"
},
{
"divider": true,
"items": [],
"name": "Single Sign-On"
},
{
"divider": false,
"items": [
{
"_id": "sso-error-response",
"type": "schemas"
},
{
"_id": "singleSignOnErrorsTrait",
"type": "traits"
}
],
"name": "Single Sign-On Shared Models and Traits"
},
{
"description": ">Twilio SendGrid Single Sign-On is currently in beta. The following documentation and product interface may change as the product is improved.\n>\n>**Known limitations during beta** \nTwilio SendGrid SSO does not currently support granting an SSO user access to more than one Subuser without granting the SSO user administrator access at the top level of your Twilio SendGrid account.\n\nThe Single Sign-On APIs allow you to manage your SAML 2.0 SSO configurations. You can also work with your SSO integrations using the [SSO section of the Twilio SendGrid App](https://app.sendgrid.com/settings/sso).\n\nThe Certificates API allows you to create, modify, and delete SSO certificates. A SAML certificate allows your IdP and Twilio SendGrid to verify requests are coming from one another using the `public_certificate` and `integration_id` parameters.\n\nFor more information about managing SSO Certificates, see the [Twilio SendGrid SSO documentation](https://sendgrid.com/docs/ui/account-and-settings/sso/).",
"divider": false,
"items": [
{
"_id": "POST_sso-certificates",
"type": "endpoints"
},
{
"_id": "GET_sso-integrations-integrationid-certificates",
"type": "endpoints"
},
{
"_id": "GET_sso-certificates-certid",
"type": "endpoints"
},
{
"_id": "PATCH_sso-certificates-certid",
"type": "endpoints"
},
{
"_id": "DELETE_sso-certificates-certid",
"type": "endpoints"
},
{
"_id": "sso-certificate-body",
"type": "schemas"
}
],
"name": "Certificates"
},
{
"description": ">Twilio SendGrid Single Sign-On is currently in beta. The following documentation and product interface may change as the product is improved.\n>\n>**Known limitations during beta** \nTwilio SendGrid SSO does not currently support granting an SSO user access to more than one Subuser without granting the SSO user administrator access at the top level of your Twilio SendGrid account.\n\nThe Single Sign-On APIs allow you to manage your SAML 2.0 SSO configurations. You can also work with your SSO integrations using the [SSO section of the Twilio SendGrid App](https://app.sendgrid.com/settings/sso).\n\nThe Single Sign-On Settings API allows you to create, retrieve, modify, and delete SSO integrations for your Twilio SendGrid account. Each integration will correspond to a specific IdP such as Okta, Duo, or Microsoft Azure Active Directory.\n\n",
"divider": false,
"items": [
{
"_id": "POST_sso-integrations",
"type": "endpoints"
},
{
"_id": "GET_sso-integrations",
"type": "endpoints"
},
{
"_id": "GET_sso-integrations-id",
"type": "endpoints"
},
{
"_id": "PATCH_sso-integrations-id",
"type": "endpoints"
},
{
"_id": "DELETE_sso-integrations-id",
"type": "endpoints"
},
{
"_id": "create-integration-request",
"type": "schemas"
},
{
"_id": "sso-integration",
"type": "schemas"
}
],
"name": "Single Sign-On Settings"
},
{
"description": ">Twilio SendGrid Single Sign-On is currently in beta. The following documentation and product interface may change as the product is improved.\n>\n>**Known limitations during beta** \nTwilio SendGrid SSO does not currently support granting an SSO user access to more than one Subuser without granting the SSO user administrator access at the top level of your Twilio SendGrid account.\n\nThe Single Sign-On APIs allow you to manage your SAML 2.0 SSO configurations. You can also work with your SSO integrations using the [SSO section of the Twilio SendGrid App](https://app.sendgrid.com/settings/sso).\n\nThe Single Sign-On Teammates API allows you to add and modify SSO Teammates. SSO Teammates are the individual user accounts who will access your Twilio SendGrid account with SSO credentials.\n\nTo retrieve or delete an SSO Teammate, you will use the [Teammates API](https://sendgrid.api-docs.io/v3.0/teammates). \n\nFor more information about managing SSO Teammates, see the [Twilio SendGrid SSO documentation](https://sendgrid.com/docs/ui/account-and-settings/sso/#manage-users).",
"divider": false,
"items": [
{
"_id": "POST_sso-teammates",
"type": "endpoints"
},
{
"_id": "PATCH_sso-teammates-username",
"type": "endpoints"
},
{
"_id": "sso-teammate-common-fields",
"type": "schemas"
},
{
"_id": "sso-teammate-request",
"type": "schemas"
},
{
"_id": "sso-teammate-response",
"type": "schemas"
},
{
"_id": "sso-teammates-patch-response",
"type": "schemas"
}
],
"name": "Single Sign-On Teammates"
},
{
"divider": true,
"items": [],
"name": "Settings"
},
{
"description": "Mail Settings instruct SendGrid to apply specific settings to every email that you send over [SendGrid’s v3 API](https://sendgrid.api-docs.io/v3.0/mail-send/v3-mail-send) or [SMTP Relay](https://sendgrid.com/docs/for-developers/sending-email/building-an-x-smtpapi-header/). These settings include how to embed a custom footer, how to manage blocks, spam, and bounces, and more.\n\nFor a full list of Twilio SendGrid's Mail Settings, and what each one does, see our [Mail Settings documentation](https://sendgrid.com/docs/ui/account-and-settings/mail/).\n\nYou can also manage your Mail Settings in the [Twilio SendGrid App](https://app.sendgrid.com/settings/mail_settings)",
"divider": false,
"items": [
{
"_id": "GET_mailsettings",
"type": "endpoints"
},
{
"_id": "PATCH_mailsettings-addresswhitelist",
"type": "endpoints"
},
{
"_id": "GET_mailsettings-addresswhitelist",
"type": "endpoints"
},
{
"_id": "PATCH_mailsettings-footer",
"type": "endpoints"
},
{
"_id": "GET_mailsettings-footer",
"type": "endpoints"
},
{
"_id": "PATCH_mailsettings-forwardspam",
"type": "endpoints"
},
{
"_id": "GET_mailsettings-forwardspam",
"type": "endpoints"
},
{
"_id": "PATCH_mailsettings-template",
"type": "endpoints"
},
{
"_id": "GET_mailsettings-template",
"type": "endpoints"
},
{
"_id": "PATCH_mailsettings-bouncepurge",
"type": "endpoints"
},
{
"_id": "GET_mailsettings-bouncepurge",
"type": "endpoints"
},
{
"_id": "PATCH_mailsettings-forwardbounce",
"type": "endpoints"
},
{
"_id": "GET_mailsettings-forwardbounce",
"type": "endpoints"
},
{
"_id": "mail_settings_patch",
"type": "schemas"
},
{
"_id": "mail_settings_address_whitelabel",
"type": "schemas"
},
{
"_id": "mail_settings_footer",
"type": "schemas"
},
{
"_id": "mail_settings_forward_spam",
"type": "schemas"
},
{
"_id": "mail_settings_template",
"type": "schemas"
},
{
"_id": "mail_settings_bounce_purge",
"type": "schemas"
},
{
"_id": "mail_settings_forward_bounce",
"type": "schemas"
}
],
"name": "Settings - Mail"
},
{
"description": "Elements that can be shared among more than one endpoint definition.",
"divider": false,
"items": [
{
"_id": "PATCH_partnersettings-newrelic",
"type": "endpoints"
},
{
"_id": "GET_partnersettings-newrelic",
"type": "endpoints"
},
{
"_id": "GET_partnersettings",
"type": "endpoints"
},
{
"_id": "partner_settings_new_relic",
"type": "schemas"
}
],
"name": "Settings - Partner"
},
{
"description": "Give and adjust account access.",
"divider": false,
"items": [
{
"_id": "POST_v3-teammates",
"type": "endpoints"
},
{
"_id": "POST_v3-teammates-pending-token-resend",
"type": "endpoints"
},
{
"_id": "GET_v3-teammates",
"type": "endpoints"
},
{
"_id": "GET_v3-scopes-requests",
"type": "endpoints"
},
{
"_id": "GET_v3-teammates-pending",
"type": "endpoints"
},
{
"_id": "GET_v3-teammates-username",
"type": "endpoints"
},
{
"_id": "PATCH_v3-scopes-requests-approve-id",
"type": "endpoints"
},
{
"_id": "PATCH_v3-teammates-username",
"type": "endpoints"
},
{
"_id": "DELETE_v3-scopes-requests-request_id",
"type": "endpoints"
},
{
"_id": "DELETE_v3-teammates-pending-token",
"type": "endpoints"
},
{
"_id": "DELETE_v3-teammates-username",
"type": "endpoints"
}
],
"name": "Teammates"
},
{
"description": "Elements that can be shared among more than one endpoint definition.",
"divider": false,
"items": [
{
"_id": "POST_alerts",
"type": "endpoints"
},
{
"_id": "GET_alerts",
"type": "endpoints"
},
{
"_id": "GET_alerts-alertid",
"type": "endpoints"
},
{
"_id": "DELETE_alerts-alertid",
"type": "endpoints"
},
{
"_id": "PATCH_alerts-alertid",
"type": "endpoints"
}
],
"name": "Alerts"
},
{
"description": "Keeping your user profile up to date helps SendGrid verify who you are and share important communications with you.\n\nYou can learn more in the [SendGrid Account Details documentation.](https://sendgrid.com/docs/ui/account-and-settings/account/)",
"divider": false,
"items": [
{
"_id": "user_profile",
"type": "schemas"
},
{
"_id": "GET_user-profile",
"type": "endpoints"
},
{
"_id": "PATCH_user-profile",
"type": "endpoints"
},
{
"_id": "GET_user-account",
"type": "endpoints"
},
{
"_id": "GET_user-email",
"type": "endpoints"
},
{
"_id": "PUT_user-email",
"type": "endpoints"
},
{
"_id": "GET_user-username",
"type": "endpoints"
},
{
"_id": "PUT_user-username",
"type": "endpoints"
},
{
"_id": "GET_user-credits",
"type": "endpoints"
},
{
"_id": "PUT_user-password",
"type": "endpoints"
}
],
"name": "Users API"
},
{
"description": "For more information about Subusers, visit the [longform Subusers documentation](https://sendgrid.com/docs/ui/account-and-settings/subusers/). You can also [manage Subusers in the SendGrid console](https://app.sendgrid.com/settings/subusers).",
"divider": false,
"items": [
{
"_id": "subuser",
"type": "schemas"
},
{
"_id": "subuser_post",
"type": "schemas"
},
{
"_id": "GET_subusers",
"type": "endpoints"
},
{
"_id": "POST_subusers",
"type": "endpoints"
},
{
"_id": "PATCH_subusers-subusername",
"type": "endpoints"
},
{
"_id": "DELETE_subusers-subusername",
"type": "endpoints"
},
{
"_id": "GET_subusers-reputations",
"type": "endpoints"
},
{
"_id": "PUT_subusers-subusername-ips",
"type": "endpoints"
}
],
"name": "Subusers API"
},
{
"description": "Subuser monitor settings allow you to receive a sample of an outgoing message from a specific customer at a specific frequency of emails.",
"divider": false,
"items": [
{
"_id": "monitor",
"type": "schemas"
},
{
"_id": "GET_subusers-subusername-monitor",
"type": "endpoints"
},
{
"_id": "POST_subusers-subusername-monitor",
"type": "endpoints"
},
{
"_id": "PUT_subusers-subusername-monitor",
"type": "endpoints"
},
{
"_id": "DELETE_subusers-subusername-monitor",
"type": "endpoints"
}
],
"name": "Subuser Monitor Settings"
},
{
"description": "Subuser statistics enable you to view specific segments of your statistics, as compared to the general overview of all email activity on your account. SendGrid tracks your subusers' emails sent, bounces, and spam reports. Unsubscribes, clicks, and opens are tracked if you have enabled the required settings.\n\nFor more information, see our [Subusers documentation](https://sendgrid.com/docs/ui/account-and-settings/subusers/). You can also access [Subuser Statistics in the SendGrid console](https://app.sendgrid.com/statistics/subuser).",
"divider": false,
"items": [
{
"_id": "subuser_stats",
"type": "schemas"
},
{
"_id": "GET_subusers-subusername-stats-monthly",
"type": "endpoints"
},
{
"_id": "GET_subusers-stats-monthly",
"type": "endpoints"
},
{
"_id": "GET_subusers-stats-sums",
"type": "endpoints"
},
{
"_id": "GET_subusers-stats",
"type": "endpoints"
}
],
"name": "Subuser Statistics"
},
{
"divider": true,
"items": [],
"name": "Deliverability"
},
{
"description": "Email link branding (formerly \"Link Whitelabel\") allows all of the click-tracked links, opens, and images in your emails to be served from your domain rather than `sendgrid.net`. Spam filters and recipient servers look at the links within emails to determine whether the email looks trustworthy. They use the reputation of the root domain to determine whether the links can be trusted.\n\nYou can also manage link branding in the [Sender Authentication section of the Twilio SendGrid App](https://app.sendgrid.com/settings/sender_auth).\n\nFor more information, please see our [Link Branding documentation](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-link-branding/).",
"divider": false,
"items": [
{
"_id": "POST_whitelabel-links",
"type": "endpoints"
},
{
"_id": "POST_whitelabel-links-id-validate",
"type": "endpoints"
},
{
"_id": "POST_whitelabel-links-linkid-subuser",
"type": "endpoints"
},
{
"_id": "GET_whitelabel-links",
"type": "endpoints"
},
{
"_id": "GET_whitelabel-links-id",
"type": "endpoints"
},
{
"_id": "GET_whitelabel-links-default",
"type": "endpoints"
},
{
"_id": "GET_whitelabel-links-subuser",
"type": "endpoints"
},
{
"_id": "PATCH_whitelabel-links-id",
"type": "endpoints"
},
{
"_id": "DELETE_whitelabel-links-id",
"type": "endpoints"
},
{
"_id": "DELETE_whitelabel-links-subuser",
"type": "endpoints"
},
{
"_id": "link_branding_200_response",
"type": "schemas"
}
],
"name": "Link branding"
},
{
"description": "IP warming is the practice of gradually increasing the volume of mail sent with a dedicated IP address according to a predetermined schedule. This gradual process helps to establish a reputation with ISPs (Internet Service Providers) as a legitimate email sender.\n\nSendGrid can automatically [warm up](https://sendgrid.com/docs/glossary/ip-warmup/) dedicated IP addresses by limiting the amount of mail that can be sent through them per hour. The limit determined by how long the IP address has been warming up. \n\nSee the [warmup schedule](https://sendgrid.com/docs/ui/sending-email/warming-up-an-ip-address/#automated-ip-warmup-hourly-send-schedule) to learn how SendGrid limits your email traffic for IPs in warmup.\n\nYou can also choose to use Twilio SendGrid's automated IP warmup for any of your IPs from the [\"IP Addresses\" settings menu in the Twilio SendGrid App](https://app.sendgrid.com/settings/ip_addresses).",
"divider": false,
"items": [
{
"_id": "POST_ips-warmup",
"type": "endpoints"
},
{
"_id": "GET_ips-warmup",
"type": "endpoints"
},
{
"_id": "GET_ips-warmup-ipaddress",
"type": "endpoints"
},
{
"_id": "DELETE_ips-warmup-ipaddress",
"type": "endpoints"
},
{
"_id": "ip_warmup_response",
"type": "schemas"
}
],
"name": "IP Warmup"
},
{
"description": "Reverse DNS (formerly IP Whitelabel) allows mailbox providers to verify the sender of an email by performing a reverse DNS lookup upon receipt of the emails you send.\n\nReverse DNS is available for [dedicated IP addresses](https://sendgrid.com/docs/ui/account-and-settings/dedicated-ip-addresses/) only.\n\nWhen setting up reverse DNS, Twilio SendGrid will provide an A Record (address record) for you to add to your DNS records. The A Record maps your sending domain to a dedicated Twilio SendGrid IP address.\n\nA Reverse DNS consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP address. Once Twilio SendGrid has verified that the appropriate A record for the IP address has been created, the appropriate reverse DNS record for the IP address is generated.\n\nYou can also manage your reverse DNS settings in the [Sender Authentication setion of the Twilio SendGrid App](https://app.sendgrid.com/settings/sender_auth).\n\nFor more about Reverse DNS, see [\"How to set up reverse DNS\"](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-reverse-dns/) in the Twilio SendGrid documentation.",
"divider": false,
"items": [
{
"_id": "POST_whitelabel-ips",
"type": "endpoints"
},
{
"_id": "POST_whitelabel-ips-id-validate",
"type": "endpoints"
},
{
"_id": "GET_whitelabel-ips",
"type": "endpoints"
},
{
"_id": "GET_whitelabel-ips-id",
"type": "endpoints"
},
{
"_id": "DELETE_whitelabel-ips-id",
"type": "endpoints"
},
{
"_id": "reverse_dns",
"type": "schemas"
}
],
"name": "Reverse DNS"
},
{
"description": "**Email Address Validation is available to Email API Pro and Premier level accounts only. Prior to upgrading your account to Pro or Premier, you will not see the option to create an Email Validation API key. An Email Validation API key is separate from and in addition to your other keys, including a Full Access API key.**\n\nEmail Address Validation provides real-time detailed information on the validity of email addresses. You can integrate this validation process into your platform's signup form and customize the best use of email address validation for your use case.\n\nYou can use this API to:\n\n* Indicate to users that the address they have entered into a form is invalid.\n* Drop invalid email addresses from your database.\n* [Suppress invalid email addresses](https://sendgrid.com/docs/ui/sending-email/index-suppressions/#different-types-of-suppressions) from your sending to decrease your bounce rate.\n\nYou can learn more about enabling Email Validation in our [Email Validation documentation](https://sendgrid.com/docs/ui/managing-contacts/email-address-validation/).\n\nYou can also view your Email Validation results and metrics in the [Validation section of the Twilio SendGrid App](https://app.sendgrid.com/email_validation). Again, these settings are available only after upgrading your account to Pro or higher.",
"divider": false,
"items": [
{
"_id": "POST_validations-email",
"type": "endpoints"
}
],
"name": "Email Address Validation"
},
{
"description": "If you don't have access to modify your companies DNS records, you can email the records to a co-worker who does to complete [Domain Authentication](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-domain-authentication/) and/or [Link Branding](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-link-branding/) setups. This email includes a direct link to the DNS records. The link does expire, but the recipient doesn't need login access to your Twilio SendGrid account.\n",
"divider": false,
"items": [
{
"_id": "POST_whitelabel-dns-email",
"type": "endpoints"
}
],
"name": "Email CNAME records"
},
{
"description": "IP pools allow you to group your dedicated SendGrid IP addresses. For example, you could create separate one pool for your transactional email and another for your marketing email. When sending marketing emails, specify that you want to use the marketing IP pool. This allows you to maintain separate reputations for your different email traffic.\n\nA single IP address or a range of IP addresses may be dedicated to an account in order to send email for multiple domains. The reputation of this IP is based on the aggregate performance of all the senders who use it.\n\nIP pools can only be used with IP addresses for which you’ve set up a reverse DNS record.\n\nIf an IP pool is *not* specified for an email, it will use any IP available, including pooled addresses.\n\n**Each user can create up to 10 different IP pools.**",
"divider": false,
"items": [
{
"_id": "POST_ips-pools",
"type": "endpoints"
},
{
"_id": "POST_ips-pools-poolname-ips",
"type": "endpoints"
},
{
"_id": "GET_ips-pools",
"type": "endpoints"
},
{
"_id": "GET_ips-pools-poolname",
"type": "endpoints"
},
{
"_id": "PUT_ips-pools-poolname",
"type": "endpoints"
},
{
"_id": "DELETE_ips-pools-poolname",
"type": "endpoints"
},
{
"_id": "DELETE_ips-pools-poolname-ips-ip",
"type": "endpoints"
},
{
"_id": "ip_pool",
"type": "schemas"
},
{
"_id": "ip_pool_response",
"type": "schemas"
}
],
"name": "IP Pools"
},
{
"description": "Elements that can be shared among more than one endpoint definition.",
"divider": false,
"items": [
{
"_id": "POST_ips",
"type": "endpoints"
},
{
"_id": "GET_ips-remaining",
"type": "endpoints"
},
{
"_id": "GET_ips",
"type": "endpoints"
},
{
"_id": "GET_ips-assigned",
"type": "endpoints"
},
{
"_id": "GET_ips-ipaddress",
"type": "endpoints"
}
],
"name": "IP Addresses"
},
{
"description": "An authenticated domain allows you to remove the “via” or “sent on behalf of” message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will get 2 TXT records and 1 MX record.\n\nDomain Authentication was formerly called \"Domain Whitelabel\".\n\nFor more information, please see [How to set up domain authentication](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-domain-authentication/).",
"divider": false,
"items": [
{
"_id": "domain_authentication:domain_spf",
"type": "schemas"
},
{
"_id": "authentication::domain",
"type": "schemas"
},
{
"_id": "domain-authentication-200-response",
"type": "schemas"
},
{
"_id": "GET_whitelabel-domains",
"type": "endpoints"
},
{
"_id": "GET_whitelabel-domains-domainid",
"type": "endpoints"
},
{
"_id": "POST_whitelabel-domains",
"type": "endpoints"
},
{
"_id": "PATCH_whitelabel-domains-domainid",
"type": "endpoints"
},
{
"_id": "DELETE_whitelabel-domains-domainid",
"type": "endpoints"
},
{
"_id": "GET_whitelabel-domains-default",
"type": "endpoints"
},
{
"_id": "POST_whitelabel-domains-id-ips",
"type": "endpoints"
},
{
"_id": "DELETE_whitelabel-domains-id-ips-ip",
"type": "endpoints"
},
{
"_id": "POST_whitelabel-domains-id-validate",
"type": "endpoints"
},
{
"_id": "GET_whitelabel-domains-subuser",
"type": "endpoints"
},
{
"_id": "DELETE_whitelabel-domains-subuser",
"type": "endpoints"
},
{
"_id": "POST_whitelabel-domains-domainid-subuser",
"type": "endpoints"
}
],
"name": "Domain Authentication"
},
{
"description": "The Sender Verification API exposes multiple endpoints that allow you to programmatically manage the [Sender Identities](https://sendgrid.com/docs/for-developers/sending-email/sender-identity/) that are authorized to send email for your account.\nYou can also manage Sender Identities in the SendGrid app by selecting [**Sender Authentication** under **Settings** in the navigation bar](https://app.sendgrid.com/settings/sender_auth). For full app instructions, see [Sender Verification](https://sendgrid.com/docs/ui/sending-email/sender-verification/).\n\nThe Sender Verification API provides a RESTful interface for creating new Sender Identities, retrieving a list of existing Sender Identities, checking the status of a Sender Identity, updating a Sender Identity, and deleting a Sender Identity.\n \nThis API offers additional endpoints to check for domains known to implement DMARC, and resend verification emails to Sender Identities that have yet to complete the verification process.",
"divider": false,
"items": [
{
"_id": "GET_verifiedsenders-domains",
"type": "endpoints"
},
{
"_id": "GET_verifiedsenders-stepscompleted",
"type": "endpoints"
},
{
"_id": "POST_verifiedsenders",
"type": "endpoints"
},
{
"_id": "GET_verifiedsenders-verify-token",
"type": "endpoints"
},
{
"_id": "PATCH_verifiedsenders-id",
"type": "endpoints"
},
{
"_id": "DELETE_verifiedsenders-id",
"type": "endpoints"
},
{
"_id": "GET_verifiedsenders",
"type": "endpoints"
},
{
"_id": "POST_verifiedsenders-resend-id",
"type": "endpoints"
},
{
"_id": "verified-sender-request-schema",
"type": "schemas"
},
{
"_id": "verified-sender-response-schema",
"type": "schemas"
}
],
"name": "Sender Verification"
},
{
"divider": true,
"items": [],
"name": "Design Library"
},
{
"description": "The Designs API offers the ability to manage assets stored in the Twilio SendGrid [Design Library](https://mc.sendgrid.com/design-library/my-designs).\n\nThe Design Library is a feature-rich email layout tool and media repository. You can [build designs for all your email needs](https://sendgrid.com/docs/ui/sending-email/working-with-marketing-campaigns-email-designs/), including Single Sends, Automations, and Dynamic Templates.\n\nYou can also duplicate and then modify one of the pre-built designs provided by Twilio SendGrid to get you started.\n\nThe Designs API provides a RESTful interface for creating new designs, retrieving a list of existing designs, duplicating or updating a design, and deleting a design.",
"divider": false,
"items": [
{
"_id": "_metadata",
"type": "schemas"
},
{
"_id": "api-error",
"type": "schemas"
},
{
"_id": "api-errors",
"type": "schemas"
},
{
"_id": "design-duplicate-input",
"type": "schemas"
},
{
"_id": "design-common-fields",
"type": "schemas"
},
{
"_id": "design-input",
"type": "schemas"
},
{
"_id": "design-output-summary",
"type": "schemas"
},
{
"_id": "design-output",
"type": "schemas"
},
{
"_id": "designsQueryStrings",
"type": "traits"
},
{
"_id": "POST-design",
"type": "endpoints"
},
{
"_id": "POST-design",
"type": "endpoints"
},
{
"_id": "LIST-designs",
"type": "endpoints"
},
{
"_id": "GET-design",
"type": "endpoints"
},
{
"_id": "POST-sendgrid-pre-built-design",
"type": "endpoints"
},
{
"_id": "LIST-Sendgrid-Pre-built-designs",
"type": "endpoints"
},
{
"_id": "GET-sendgrid-pre-built-design",
"type": "endpoints"
},
{
"_id": "PUT-design",
"type": "endpoints"
},
{
"_id": "DELETE-design",
"type": "endpoints"
}
],
"name": "Designs API"
},
{
"divider": true,
"items": [],
"name": "New Marketing Campaigns"
},
{
"divider": false,
"items": [
{
"_id": "contact-import",
"type": "schemas"
},
{
"_id": "single-contact-request",
"type": "schemas"
},
{
"_id": "contact-export",
"type": "schemas"
},
{
"_id": "contact-summary",
"type": "schemas"
},
{
"_id": "contact-request",
"type": "schemas"
},
{
"_id": "contact-details",
"type": "schemas"
},
{
"_id": "contact-details2",
"type": "schemas"
},
{
"_id": "contact-details3",
"type": "schemas"
},
{
"_id": "PUT_mc-contacts",
"type": "endpoints"
},
{
"_id": "DELETE_mc-contacts",
"type": "endpoints"
},
{
"_id": "GET_mc-contacts-count",
"type": "endpoints"
},
{
"_id": "POST_mc-contacts-exports",
"type": "endpoints"
},
{
"_id": "GET_mc-contacts-id",
"type": "endpoints"
},
{
"_id": "POST_mc-contacts-search",
"type": "endpoints"
},
{
"_id": "GET_mc-contats",
"type": "endpoints"
},
{
"_id": "PUT_mc-contacts-imports",
"type": "endpoints"
},
{
"_id": "GET_marketing-contacts-imports-id",
"type": "endpoints"
},
{
"_id": "GET_mc-contacts-exports-id",
"type": "endpoints"
},
{
"_id": "GET_marketing-contacts-exports",
"type": "endpoints"
},
{
"_id": "POST_marketing-contacts-batch",
"type": "endpoints"
},
{
"_id": "POST_marketing-contacts-search-emails",
"type": "endpoints"
},
{
"_id": "tip",
"type": "docTextSections"
}
],
"name": "Contacts"
},
{
"divider": false,
"items": [
{
"_id": "segment_status_response",
"type": "schemas"
},
{
"_id": "all_segments_response",
"type": "schemas"
},
{
"_id": "segment_summary_v2",
"type": "schemas"
},
{
"_id": "contact_response",
"type": "schemas"
},
{
"_id": "segment_response",
"type": "schemas"
},
{
"_id": "errors-seg-v2",
"type": "schemas"
},
{
"_id": "segment_write_v2",
"type": "schemas"
},
{
"_id": "segment_update",
"type": "schemas"
},
{
"_id": "_metadata",
"type": "schemas"
},
{
"_id": "POST_segments",
"type": "endpoints"
},
{
"_id": "errors",
"type": "traits"
},
{
"_id": "PATCH_segments-segment_id",
"type": "endpoints"
},
{
"_id": "GET_segments",
"type": "endpoints"
},
{
"_id": "GET_segments-segment_id",
"type": "endpoints"
},
{
"_id": "DELETE_segments-segment_id",
"type": "endpoints"
}
],
"name": "Segmenting Contacts V2 - Beta"
},
{
"divider": false,
"items": [
{
"_id": "senders-id-request-body",
"type": "schemas"
},
{
"_id": "TNE-senderID",
"type": "schemas"
},
{
"_id": "POST_marketing-senders",
"type": "endpoints"
}
],
"name": "Senders"
},
{
"description": "Lists are static collections of Marketing Campaigns contacts. This API allows you to interact with the list objects themselves. To add contacts to a list, you must use the [Contacts API](https://sendgrid.api-docs.io/v3.0/contacts).\n\nYou can also manage your lists using the [Contacts menu in the Marketing Campaigns UI](https://mc.sendgrid.com/contacts). For more information about lists and best practices for building them, see [\"Building your Contact List\"](https://sendgrid.com/docs/ui/managing-contacts/building-your-contact-list/).",
"divider": false,
"items": [
{
"_id": "list",
"type": "schemas"
},
{
"_id": "POST_mc-lists",
"type": "endpoints"
},
{
"_id": "GET_mc-lists-id-contacts-count",
"type": "endpoints"
},
{
"_id": "GET_mc-lists-id",
"type": "endpoints"
},
{
"_id": "GET_mc-lists",
"type": "endpoints"
},
{
"_id": "PATCH_mc-lists-id",
"type": "endpoints"
},
{
"_id": "DELETE_lists-id",
"type": "endpoints"
},
{
"_id": "DELETE_mc-lists-id-contacts",
"type": "endpoints"
}
],
"name": "Lists"
},
{
"description": "Custom Fields allow you to add extra information about your contacts to your contact database. With custom fields, you can create custom segments from your individual contacts or from your contact database that will dynamically update your content with the values for the individual contact receiving the email. Your custom fields are completely customizable to the use cases and user information that you need.\n\nYou can also manage your Custom Fields using the [Custom Fields UI in the Marketing Campaigns App](https://mc.sendgrid.com/custom-fields). For more about creating Custom Fields, including a list of Reserved Fields, see our [Custom Fields documentation](https://sendgrid.com/docs/ui/managing-contacts/custom-fields/).",
"divider": false,
"items": [
{
"_id": "custom-fields-by-name",
"type": "schemas"
},
{
"_id": "custom-fields-by-id",
"type": "schemas"
},
{
"_id": "reserved_field_definitions_response",
"type": "schemas"
},
{
"_id": "custom_field_definitions_response",
"type": "schemas"
},
{
"_id": "POST_mc-field_definitions",
"type": "endpoints"
},
{
"_id": "GET_mc-field_definitions",
"type": "endpoints"
},
{
"_id": "PATCH_mc-field_definitions-custom_field_id",
"type": "endpoints"
},
{
"_id": "DELETE_mc-field_definitions-custom_field_id",
"type": "endpoints"
}
],
"name": "Custom Fields"
},
{
"description": "Segments are similar to contact lists, except they update dynamically over time as information stored about your contacts or the criteria used to define your segments changes. When you segment your audience, you are able to create personalized Automation emails and Single Sends that directly address the wants and needs of your particular audience.\n\nThe Marketing Campaigns Segments API allows you to create, edit, and delete segments as well as retrieve a list of segments or an individual segment by ID.\n\n> Note that Twilio SendGrid checks for newly added or modified contacts who meet a segment's criteria on an hourly schedule. Only existing contacts who meet a segment's criteria will be included in the segment searches within 15 minutes.\n>\n> Segments built using engagement data such as \"was sent\" or \"clicked\" will take approximately 30 minutes to begin populating.\n>\n> Segment samples and counts are refreshed approximately once per hour; they do not update immediately. If no contacts are added to or removed from a segment since the last refresh, the sample and UI count displayed will be refreshed at increasing time intervals with a maximum sample and count refresh delay of 24 hours.\n\nFor more information on creating segments with the Twilio SendGrid Marketing Campaigns UI see [\"Segmenting your Contacts.\"](https://sendgrid.com/docs/ui/managing-contacts/segmenting-your-contacts/)\n\nFor help with Segmentation Query Language, see our [Segmentation Query Language reference](https://sendgrid.com/docs/for-developers/sending-email/segmentation-query-language/)",
"divider": false,
"items": [
{
"_id": "xJZx99h8rizghwRct",
"type": "traits"
},
{
"_id": "segment_write",
"type": "schemas"
},
{
"_id": "contact_response",
"type": "schemas"
},
{
"_id": "segment_summary",
"type": "schemas"
},
{
"_id": "full-segment",
"type": "schemas"
},
{
"_id": "segment_query_json",
"type": "schemas"
},
{
"_id": "POST_marketing-segments",
"type": "endpoints"
},
{
"_id": "GET_marketing-segments-segmentid",
"type": "endpoints"
},
{
"_id": "GET_marketing-segments",
"type": "endpoints"
},
{
"_id": "PATCH_marketing-segments-segmentid",
"type": "endpoints"
},
{
"_id": "DELETE_marketing-segments-segmentid",
"type": "endpoints"
},
{
"_id": "POST_marketing-segments-delete",
"type": "endpoints"
}
],
"name": "Segmenting Contacts"
},
{
"description": "A Single Send is a one-time nonautomated email message delivered to a list or segment of your audience. A Single Send may be sent immediately or scheduled for future delivery.\n\nSingle Sends can serve many use cases, including promotional offers, engagement campaigns, newsletters, announcements, legal notices, or policy updates.\n\nThe Single Sends API allows you to create, retrieve, update, delete, schedule, and deliver your Single Sends. There are also endpoints for searching and statistics to help you maintain and alter your Single Sends as you learn more and further develop your campaigns.\n\nThe Single Sends API changed on **May 6, 2020**. Please check the SendGrid Knowledge Center for updates and instructions here: [https://sendgrid.com/docs/for-developers/sending-email/single-sends-2020-update/](https://sendgrid.com/docs/for-developers/sending-email/single-sends-2020-update/)",
"divider": false,
"items": [
{
"_id": "vve7kMhgurSwPWi6S",
"type": "schemas"
},
{
"_id": "FTA2YcjxEPyAFfYe8",
"type": "schemas"
},
{
"_id": "singlesend_request",
"type": "schemas"
},
{
"_id": "singlesend_response_short",
"type": "schemas"
},
{
"_id": "singlesend_response",
"type": "schemas"
},
{
"_id": "singlesend_search",
"type": "schemas"
},
{
"_id": "singlesend_schedule",
"type": "schemas"
},
{
"_id": "singlesend_warning",
"type": "schemas"
},
{
"_id": "abtest_summary",
"type": "schemas"
},
{
"_id": "POST_marketing-singlesends",
"type": "endpoints"
},
{
"_id": "POST_marketing-singlesends-id",
"type": "endpoints"
},
{
"_id": "POST_marketing-singlesends-search",
"type": "endpoints"
},
{
"_id": "PATCH_marketing-singlesends-id",
"type": "endpoints"
},
{
"_id": "PUT_marketing-singlesends-id-schedule",
"type": "endpoints"
},
{
"_id": "GET_marketing-singlesends",
"type": "endpoints"
},
{
"_id": "GET_marketing-singlesends-id",
"type": "endpoints"
},
{
"_id": "GET_marketing-singlesends-categories",
"type": "endpoints"
},
{
"_id": "DELETE_marketing-singlesends-id",
"type": "endpoints"
},
{
"_id": "DELETE_marketing-singlesends",
"type": "endpoints"
},
{
"_id": "DELETE_marketing-singlesends-id-schedule",
"type": "endpoints"
}
],
"name": "Single Sends"
},
{
"divider": false,
"items": [
{
"_id": "POST_marketing-test-sendemail",
"type": "endpoints"
}
],
"name": "Send Test Email"
},
{
"description": "The Marketing Campaigns Stats endpoints allow you to retrieve stats for both Automations and Single Sends.\n\n**Note:** These endpoints provide stats for Marketing Campaigns only. For stats related to event tracking, please see the **Stats** section under **Event Tracking** below.",
"divider": false,
"items": [
{
"_id": "automationQueryParams",
"type": "traits"
},
{
"_id": "singlesendQueryParams",
"type": "traits"
},
{
"_id": "singlesendQueryParams2",
"type": "traits"
},
{
"_id": "errorResponse",
"type": "traits"
},
{
"_id": "baseParams",
"type": "traits"
},
{
"_id": "pagination",
"type": "traits"
},
{
"_id": "errors",
"type": "schemas"
},
{
"_id": "metadata",
"type": "schemas"
},
{
"_id": "metrics",
"type": "schemas"
},
{
"_id": "singlesends-response",
"type": "schemas"
},
{
"_id": "automations-response",
"type": "schemas"
},
{
"_id": "automations-link-stats-response",
"type": "schemas"
},
{
"_id": "singlesends-link-stats-response",
"type": "schemas"
},
{
"_id": "link-tracking-metadata",
"type": "schemas"
},
{
"_id": "getall-automation-stats",
"type": "endpoints"
},
{
"_id": "get-automation-stat",
"type": "endpoints"
},
{
"_id": "getall-singlesend-stats",
"type": "endpoints"
},
{
"_id": "get-singlesend-stat",
"type": "endpoints"
},
{
"_id": "get-automation-link-stat",
"type": "endpoints"
},
{
"_id": "get-singlesend-link-stat",
"type": "endpoints"
},
{
"_id": "get-singlesend-stats-export",
"type": "endpoints"
},
{
"_id": "get-automations-stats-export",
"type": "endpoints"
}
],
"name": "Marketing Campaigns Stats"
},
{
"divider": true,
"items": [],
"name": "Legacy Marketing Campaigns"
},
{
"description": "A [Sender Identity](https://sendgrid.com/docs/for-developers/sending-email/sender-identity/) is the email address from which an email is sent. Sender Identities must be verified before use.\n\nIf your domain has been authenticated, it will auto verify on creation. Otherwise, a verification email will be sent to the `from.email`.",
"divider": false,
"items": [
{
"_id": "senderID",
"type": "schemas"
},
{
"_id": "sender-id-request",
"type": "schemas"
},
{
"_id": "POST_senders",
"type": "endpoints"
},
{
"_id": "GET_v3-senders",
"type": "endpoints"
},
{
"_id": "GET_v3-senders-sender_id",
"type": "endpoints"
},
{
"_id": "PATCH_v3-senders-sender_id",
"type": "endpoints"
},
{
"_id": "POST_v3-senders-sender_id-resend_verification",
"type": "endpoints"
},
{
"_id": "DELETE_v3-senders-sender_id",
"type": "endpoints"
}
],
"name": "Sender Identities API"
},
{
"description": "Elements that can be shared among more than one endpoint definition.",
"divider": false,
"items": [
{
"_id": "contactdb_list",
"type": "schemas"
},
{
"_id": "POST_contactdb-lists",
"type": "endpoints"
},
{
"_id": "GET_contactdb-lists",
"type": "endpoints"
},
{
"_id": "DELETE_contactdb-lists",
"type": "endpoints"
},
{
"_id": "GET_contactdb-lists-listid",
"type": "endpoints"
},
{
"_id": "PATCH_contactdb-lists-listid",
"type": "endpoints"
},
{
"_id": "DELETE_contactdb-lists-listid",
"type": "endpoints"
},
{
"_id": "GET_contactdb-lists-listid-recipients",
"type": "endpoints"
},
{
"_id": "POST_contactdb-lists-listid-recipients-recipientid",
"type": "endpoints"
},
{
"_id": "DELETE_contactdb-lists-listid-recipients-recipientid",
"type": "endpoints"
},
{
"_id": "POST_contactdb-lists-listid-recipients",
"type": "endpoints"
}
],
"name": "Contacts API - Lists"
},
{
"description": "Elements that can be shared among more than one endpoint definition.",
"divider": false,
"items": [
{
"_id": "contactdb_recipient_count",
"type": "schemas"
},
{
"_id": "contactdb_recipient",
"type": "schemas"
},
{
"_id": "contactdb_recipient_response",
"type": "schemas"
},
{
"_id": "contacts",
"type": "schemas"
},
{
"_id": "POST_contactdb-recipients",
"type": "endpoints"
},
{
"_id": "GET_contactdb-status",
"type": "endpoints"
},
{
"_id": "PATCH_contactdb-recipients",
"type": "endpoints"
},
{
"_id": "DELETE_contactdb-recipients",
"type": "endpoints"
},
{
"_id": "GET_contactdb-recipients",
"type": "endpoints"
},
{
"_id": "GET_contactdb-recipients-recipientid",
"type": "endpoints"
},
{
"_id": "DELETE_contactdb-recipients-recipientid",
"type": "endpoints"
},
{
"_id": "GET_contactdb-recipients-recipientid-lists",
"type": "endpoints"
},
{
"_id": "GET_contactdb-recipients-billablecount",
"type": "endpoints"
},
{
"_id": "GET_contactdb-recipients-count",
"type": "endpoints"
},
{
"_id": "GET_contactdb-recipients-search",
"type": "endpoints"
},
{
"_id": "POST_contactdb-recipients-search",
"type": "endpoints"
}
],
"name": "Contacts API - Recipients"
},
{
"description": "Elements that can be shared among more than one endpoint definition.",
"divider": false,
"items": [
{
"_id": "contactdb_custom_field_with_id",
"type": "schemas"
},
{
"_id": "contactdb_custom_field_with_id_value",
"type": "schemas"
},
{
"_id": "contactdb_custom_field",
"type": "schemas"
},
{
"_id": "POST_contactdb-customfields",
"type": "endpoints"
},
{
"_id": "GET_contactdb-customfields",
"type": "endpoints"
},
{
"_id": "GET_contactdb-customfields-customfieldid",
"type": "endpoints"
},
{
"_id": "DELETE_contactdb-customfields-customfieldid",
"type": "endpoints"
},
{
"_id": "GET_contactdb-reservedfields",
"type": "endpoints"
}
],
"name": "Contacts API - Custom Fields"
},
{
"description": "Elements that can be shared among more than one endpoint definition.",
"divider": false,
"items": [
{
"_id": "contactdb_segments",
"type": "schemas"
},
{
"_id": "contactdb_segments_with_id",
"type": "schemas"
},
{
"_id": "contactdb_segments_conditions",
"type": "schemas"
},
{
"_id": "POST_contactdb-segments",
"type": "endpoints"
},
{
"_id": "GET_contactdb-segments",
"type": "endpoints"
},
{
"_id": "GET_contactdb-segments-segmentid",
"type": "endpoints"
},
{
"_id": "PATCH_contactdb-segments-segmentid",
"type": "endpoints"
},
{
"_id": "DELETE_contactdb-segments-segmentid",
"type": "endpoints"
},
{
"_id": "GET_contactdb-segments-segmentid-recipients",
"type": "endpoints"
}
],
"name": "Contacts API - Segments"
},
{
"description": "Categories can help organize your email analytics by enabling you to “tag” emails by type or broad topic. You can define your own custom categories.\n\nYou can retrieve statistics based on your categories, but note that category statistics are only available for the previous thirteen months.",
"divider": false,
"items": [
{
"_id": "GET_categories",
"type": "endpoints"
},
{
"_id": "GET_categories-stats-sums",
"type": "endpoints"
},
{
"_id": "GET_categories-stats",
"type": "endpoints"
},
{
"_id": "category_stats",
"type": "schemas"
}
],
"name": "Categories"
},
{
"description": "Allows you to create, manage, send, and schedule campaigns.",
"divider": false,
"items": [
{
"_id": "campaign_request",
"type": "schemas"
},
{
"_id": "campaign_response",
"type": "schemas"
},
{
"_id": "POST_campaigns",
"type": "endpoints"
},
{
"_id": "GET_campaigns",
"type": "endpoints"
},
{
"_id": "GET_campaigns-campaignid",
"type": "endpoints"
},
{
"_id": "DELETE_campaigns-campaignid",
"type": "endpoints"
},
{
"_id": "PATCH_campaigns-campaignid",
"type": "endpoints"
},
{
"_id": "POST_campaigns-campaignid-schedules-now",
"type": "endpoints"
},
{
"_id": "POST_campaigns-campaignid-schedules",
"type": "endpoints"
},
{
"_id": "PATCH_campaigns-campaignid-schedules",
"type": "endpoints"
},
{
"_id": "GET_campaigns-campaignid-schedules",
"type": "endpoints"
},
{
"_id": "DELETE_campaigns-campaignid-schedules",
"type": "endpoints"
},
{
"_id": "POST_campaigns-campaignid-schedules-test",
"type": "endpoints"
}
],
"name": "Campaigns API"
},
{
"divider": true,
"items": [],
"name": "Templates"
},
{
"description": "An HTML template that can establish a consistent design for [transactional emails](https://sendgrid.com/use-cases/transactional-email/).\n\nEach user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts.\n\nTransactional templates are templates created specifically for transactional email and are not to be confused with [Marketing Campaigns designs](https://sendgrid.com/docs/ui/sending-email/working-with-marketing-campaigns-email-designs/). For more information about transactional templates, please see our [Dynamic Transactional Templates documentation](https://sendgrid.com/docs/ui/sending-email/how-to-send-an-email-with-dynamic-transactional-templates/).",
"divider": false,
"items": [
{
"_id": "transactional_template",
"type": "schemas"
},
{
"_id": "transactional-templates-template-lean",
"type": "schemas"
},
{
"_id": "transactional-template-warning",
"type": "schemas"
},
{
"_id": "POST_templates",
"type": "endpoints"
},
{
"_id": "POST_templates-templateid",
"type": "endpoints"
},
{
"_id": "GET_templates",
"type": "endpoints"
},
{
"_id": "GET_templates-templateid",
"type": "endpoints"
},
{
"_id": "PATCH_templates-templateid",
"type": "endpoints"
},
{
"_id": "DELETE_templates-templateid",
"type": "endpoints"
}
],
"name": "Transactional Templates"
},
{
"description": "Represents the code for a particular transactional template. Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates.\n\nFor more information about transactional templates, please see our [Transactional Templates documentation](https://sendgrid.com/docs/ui/sending-email/how-to-send-an-email-with-dynamic-transactional-templates/). You can also manage your Transactional Templates in the [Dynamic Templates section of the Twilio SendGrid App](https://mc.sendgrid.com/dynamic-templates).",
"divider": false,
"items": [
{
"_id": "transactional-templates-version-output-lean",
"type": "schemas"
},
{
"_id": "transactional_template_version_create",
"type": "schemas"
},
{
"_id": "transactional_template_version_output",
"type": "schemas"
},
{
"_id": "POST_templates-templateid-versions",
"type": "endpoints"
},
{
"_id": "POST_templates-templateid-versions-versionid-activate",
"type": "endpoints"
},
{
"_id": "GET_templates-templateid-versions-versionid",
"type": "endpoints"
},
{
"_id": "PATCH_templates-templateid-versions-versionid",
"type": "endpoints"
},
{
"_id": "DELETE_templates-templateid-versions-versionid",
"type": "endpoints"
}
],
"name": "Transactional Templates Versions"
},
{
"divider": true,
"items": [],
"name": "Event Tracking"
},
{
"divider": false,
"items": [
{
"_id": "webhooks-event-webhook-request",
"type": "schemas"
},
{
"_id": "event-webhook-response",
"type": "schemas"
},
{
"_id": "event-webhook-update-oauth-request",
"type": "schemas"
},
{
"_id": "GET_user-webhooks-event-settings",
"type": "endpoints"
},
{
"_id": "GET_user-webhooks-parse-settings",
"type": "endpoints"
},
{
"_id": "GET_user-webhooks-parse-stats",
"type": "endpoints"
},
{
"_id": "GET_user-webhooks-event-settings-signed",
"type": "endpoints"
},
{
"_id": "POST_user-webhooks-event-test",
"type": "endpoints"
},
{
"_id": "PATCH_user-webhooks-event-settings",
"type": "endpoints"
},
{
"_id": "PATCH_user-webhooks-event-settings-signed",
"type": "endpoints"
}
],
"name": "Webhooks"
},
{
"description": "**You must purchase [additional email activity history](https://app.sendgrid.com/settings/billing/addons/email_activity) to gain access to the Email Activity Feed API.**\n\nThe Email Activity API allows you to query all of your stored messages, query individual messages, and download a CSV with data about the stored messages.\n\nOnce retrieved, you can inspect the data associated with your messages to better understand your mail send. For example, you may retrieve all bounced messages or all messages with the same subject line and search for commonalities among them.\n\nSee \"[Getting Started with the Email Activity Feed API](https://sendgrid.com/docs/for-developers/sending-email/getting-started-email-activity-api/)\" for help building queries and working with the API.\n\nYou can also work with email activity in the **Activity** section of the [Twilio SendGrid App](https://app.sendgrid.com/email_activity).",
"divider": false,
"items": [
{
"_id": "GET-messages",
"type": "endpoints"
},
{
"_id": "GET-v3-messages-msg_id",
"type": "endpoints"
},
{
"_id": "POST_v3-messages-download",
"type": "endpoints"
},
{
"_id": "GET_v3-messages-download-download_uuid",
"type": "endpoints"
},
{
"_id": "email-activity-response-common-fields",
"type": "schemas"
}
],
"name": "Email Activity"
},
{
"description": "You can track many of your recipients' interactions with your emails, including:\n\n- Opening emails\n- Clicking links\n- Subscribing to (or unsubscribing from) your emails\n\nFor more information about tracking, please see our [Tracking Settings documentation](https://sendgrid.com/docs/ui/account-and-settings/tracking/).",
"divider": false,
"items": [
{
"_id": "subscription_tracking_settings",
"type": "schemas"
},
{
"_id": "click-tracking",
"type": "schemas"
},
{
"_id": "google_analytics_settings",
"type": "schemas"
},
{
"_id": "GET_trackingsettings",
"type": "endpoints"
},
{
"_id": "GET_trackingsettings-click",
"type": "endpoints"
},
{
"_id": "PATCH_trackingsettings-click",
"type": "endpoints"
},
{
"_id": "GET_trackingsettings-googleanalytics",
"type": "endpoints"
},
{
"_id": "PATCH_trackingsettings-googleanalytics",
"type": "endpoints"
},
{
"_id": "GET_trackingsettings-open",
"type": "endpoints"
},
{
"_id": "PATCH_trackingsettings-open",
"type": "endpoints"
},
{
"_id": "GET_trackingsettings-subscription",
"type": "endpoints"
},
{
"_id": "PATCH_trackingsettings-subscription",
"type": "endpoints"
}
],
"name": "Settings - Tracking"
},
{
"description": "Tracking your emails is an important part of being a good sender and learning about how your users interact with your email. This includes everything from basics of clicks and opens to looking at which browsers and mailbox providers your customers use.\n\nWe have broken up statistics in specific ways so that you can get at-a-glance data, as well as allowing you to get into the details of how your email is being used.\n\n> Category statistics are available for the previous thirteen months only.",
"divider": false,
"items": [
{
"_id": "Ag8mEYHMm5BNuLh4B",
"type": "schemas"
},
{
"_id": "statsAdvancedStatsBaseQueryStrings",
"type": "traits"
},
{
"_id": "statsAdvancedQueryStringsLimitOffset",
"type": "traits"
},
{
"_id": "advanced_stats_mailbox_provider",
"type": "schemas"
},
{
"_id": "stats-advanced-global-stats",
"type": "schemas"
},
{
"_id": "GET_stats",
"type": "endpoints"
},
{
"_id": "GET_geo-stats",
"type": "endpoints"
},
{
"_id": "GET_devices-stats",
"type": "endpoints"
},
{
"_id": "GET_clients-stats",
"type": "endpoints"
},
{
"_id": "GET_clients-clienttype-stats",
"type": "endpoints"
},
{
"_id": "GET_mailboxproviders-stats",
"type": "endpoints"
},
{
"_id": "GET_browsers-stats",
"type": "endpoints"
},
{
"_id": "stats-advanced-stats-base-schema",
"type": "schemas"
},
{
"_id": "advanced_stats_clicks",
"type": "schemas"
},
{
"_id": "advanced_stats_opens",
"type": "schemas"
},
{
"_id": "advanced_stats_clicks_opens",
"type": "schemas"
}
],
"name": "Stats"
},
{
"divider": true,
"items": [],
"name": "Suppression Management"
},
{
"description": "An email is considered [bounced](https://sendgrid.com/docs/glossary/bounces/) when the message is undeliverable and then returned to the server that sent it. Bounced emails can be either permanent or temporary failures to deliver the message.\n\nFor more information, see our [Bounces documentation](https://sendgrid.com/docs/ui/sending-email/bounces/).\n\nYou can also manage bounced emails from the [Suppression settings menu in the Twilio SendGrid App](https://app.sendgrid.com/suppressions/bounces).",
"divider": false,
"items": [
{
"_id": "GET_suppression-bounces",
"type": "endpoints"
},
{
"_id": "GET_suppression-bounces-email",
"type": "endpoints"
},
{
"_id": "DELETE_suppression-bounces",
"type": "endpoints"
},
{
"_id": "DELETE_suppression-bounces-email",
"type": "endpoints"
},
{
"_id": "bounce_response",
"type": "schemas"
}
],
"name": "Bounces API"
},
{
"description": "[Blocks](https://sendgrid.com/docs/glossary/blocks/) happen when your email is rejected because of an issue with the message itself rather than an issue with the recipient's address.\n\nThere are several causes for blocked emails. For example, your mail server IP address may be blocked by an ISP, or the receiving server may flag the message content using a filter. Twilio SendGrid will not suppress future messages to blocked addresses by default. \n\nFor more information, please see our [Blocks documentation](https://sendgrid.com/docs/ui/sending-email/blocks/).\n\nYou can also see your Blocks in the [Suppressions settings menu of the Twilio SendGrid App](https://app.sendgrid.com/suppressions/blocks).",
"divider": false,
"items": [
{
"_id": "GET_suppression-blocks",
"type": "endpoints"
},
{
"_id": "GET_suppression-blocks-email",
"type": "endpoints"
},
{
"_id": "DELETE_suppression-blocks",
"type": "endpoints"
},
{
"_id": "DELETE_suppression-blocks-email",
"type": "endpoints"
},
{
"_id": "blocks-response",
"type": "schemas"
}
],
"name": "Blocks API"
},
{
"description": "Spam Reports are triggered when a recipient marks one of your emails as spam. Spam reports can only be gathered from Internet Service Providers (ISPs) that provide a feedback loop.\n\nIt is important that addresses that have marked your messages as spam be permanently removed from your send list, even if the recipients have previously opted into receiving your messages. Continuing to send to customers who have reported your email as spam can severely affect your deliverability rating.\n\nYou can also access your Spam Reports from the [Suppressions settings menu in the Twilio SendGrid App](https://app.sendgrid.com/suppressions/spam_reports).\n\nFor more information, please see our [Spam Reports documentation](https://sendgrid.com/docs/ui/analytics-and-reporting/spam-reports/).",
"divider": false,
"items": [
{
"_id": "GET_suppression-spamreports",
"type": "endpoints"
},
{
"_id": "GET_suppression-spamreports-email",
"type": "endpoints"
},
{
"_id": "DELETE_suppression-spamreports",
"type": "endpoints"
},
{
"_id": "DELETE_suppression-spamreports-email",
"type": "endpoints"
},
{
"_id": "spam-reports-response",
"type": "schemas"
}
],
"name": "Spam Reports API"
},
{
"description": "A global suppression (or global unsubscribe) is an email address of a recipient who does not want to receive any of your messages. A globally suppressed recipient will be removed from any email you send. For more information, please see our [Global Unsubscribes documentation](https://sendgrid.com/docs/ui/sending-email/global-unsubscribes/).",
"divider": false,
"items": [
{
"_id": "POST_asm-suppressions-global",
"type": "endpoints"
},
{
"_id": "GET_suppression-unsubscribes",
"type": "endpoints"
},
{
"_id": "GET_asm-suppressions-global-email",
"type": "endpoints"
},
{
"_id": "DELETE_asm-suppressions-global-email",
"type": "endpoints"
},
{
"_id": "suppressions-request",
"type": "schemas"
}
],
"name": "Suppressions - Global Suppressions"
},
{
"description": "Suppression groups, or unsubscribe groups, are specific types or categories of emails from which you would like your recipients to be able to unsubscribe. For example: Daily Newsletters, Invoices, and System Alerts are all potential suppression groups. Visit the main documentation to [learn more about suppression/unsubscribe groups](https://sendgrid.com/docs/ui/sending-email/unsubscribe-groups/)\n\nThe **name** and **description** of the unsubscribe group will be visible by recipients when they are managing their subscriptions.\n\nEach Twilio SendGrid account can create up to 25 different suppression groups.",
"divider": false,
"items": [
{
"_id": "POST_asm-groups",
"type": "endpoints"
},
{
"_id": "GET_asm-groups",
"type": "endpoints"
},
{
"_id": "GET_asm-groups-groupid",
"type": "endpoints"
},
{
"_id": "PATCH_asm-groups-groupid",
"type": "endpoints"
},
{
"_id": "DELETE_asm-groups-groupid",
"type": "endpoints"
},
{
"_id": "suppression-group-request-base",
"type": "schemas"
},
{
"_id": "suppression_group",
"type": "schemas"
}
],
"name": "Suppressions - Unsubscribe Groups"
},
{
"description": "Suppressions are recipient email addresses that are added to [unsubscribe groups](https://sendgrid.com/docs/ui/sending-email/unsubscribe-groups/). Once a recipient's address is on the suppressions list for an unsubscribe group, they will not receive any emails that are tagged with that unsubscribe group.",
"divider": false,
"items": [
{
"_id": "POST_asm-groups-groupid-suppressions",
"type": "endpoints"
},
{
"_id": "POST_asm-groups-groupid-suppressions-search",
"type": "endpoints"
},
{
"_id": "GET_asm-groups-groupid-suppressions",
"type": "endpoints"
},
{
"_id": "GET_asm-suppressions",
"type": "endpoints"
},
{
"_id": "GET_asm-suppressions-email",
"type": "endpoints"
},
{
"_id": "DELETE_asm-groups-groupid-suppressions-email",
"type": "endpoints"
}
],
"name": "Suppressions - Suppressions"
},
{
"description": "\nAn invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipient’s mail server.\n\nExamples include addresses without the “@” sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server.\n\nFor more information, please see our [Invalid Email documentation](https://sendgrid.com/docs/ui/sending-email/invalid-emails/).",
"divider": false,
"items": [
{
"_id": "GET_suppression-invalidemails",
"type": "endpoints"
},
{
"_id": "GET_suppression-invalidemails-email",
"type": "endpoints"
},
{
"_id": "DELETE_suppression-invalidemails",
"type": "endpoints"
},
{
"_id": "DELETE_suppression-invalidemails-email",
"type": "endpoints"
},
{
"_id": "invalid-email",
"type": "schemas"
}
],
"name": "Invalid Emails API"
},
{
"divider": true,
"items": [],
"name": "Inbound Parse"
},
{
"description": "Twilio SendGrid’s Inbound Parse Webhook allows you to receive emails as multipart/form-data at a URL of your choosing. SendGrid will grab the content, attachments, and the headers from any email it receives for your specified hostname.\n\nSee \"[Setting up the Inbound Parse Webhook](https://sendgrid.com/docs/for-developers/parsing-email/setting-up-the-inbound-parse-webhook/)\" for help configuring the Webhook. You can also manage the Inbound Parse Webhook in the [Twilio SendGrid App](https://app.sendgrid.com/settings/parse).\n\nTo begin processing email using SendGrid's Inbound Parse Webhook, you will have to setup MX Records, choose the hostname (or receiving domain) that will be receiving the emails you want to parse, and define the URL where you want to POST your parsed emails. If you do not have access to your domain's DNS records, you must work with someone in your organization who does.",
"divider": false,
"items": [
{
"_id": "parse-setting",
"type": "schemas"
},
{
"_id": "POST_user-webhooks-parse-settings",
"type": "endpoints"
},
{
"_id": "GET_user-webhooks-parse-settings",
"type": "endpoints"
},
{
"_id": "GET_user-webhooks-parse-settings-hostname",
"type": "endpoints"
},
{
"_id": "PATCH_user-webhooks-parse-settings-hostname",
"type": "endpoints"
},
{
"_id": "DELETE_user-webhooks-parse-settings-hostname",
"type": "endpoints"
}
],
"name": "Settings - Inbound Parse"
},
{
"description": "Elements that can be shared among more than one endpoint definition.",
"divider": false,
"items": [
{
"_id": "global_error_response_schema",
"type": "schemas"
},
{
"_id": "global:id",
"type": "schemas"
},
{
"_id": "global:empty_request",
"type": "schemas"
},
{
"_id": "selfmetadata",
"type": "schemas"
},
{
"_id": "error",
"type": "schemas"
},
{
"_id": "link",
"type": "schemas"
},
{
"_id": "metadata",
"type": "schemas"
},
{
"_id": "webhook",
"type": "schemas"
}
],
"name": "Global: Models"
},
{
"description": "Elements that can be shared among more than one endpoint definition.",
"divider": false,
"items": [
{
"_id": "onBehalfOfSubuser",
"type": "traits"
},
{
"_id": "globalErrors",
"type": "traits"
},
{
"_id": "makoErrorResponse",
"type": "traits"
}
],
"name": "Global: Traits"
},
{
"divider": false,
"items": [
{
"_id": "credentials",
"type": "schemas"
}
],
"name": "Credentials"
}
]
}
}
},
"x-tests": {
"2QtdGPeZcwPm7CTTh": {
"id": "2QtdGPeZcwPm7CTTh",
"initialVariables": {},
"name": "Alerts test",
"steps": [
{
"assertions": [
{
"expected": 201,
"location": "response.code",
"op": "eq"
},
{
"location": "response.body",
"match": 201,
"op": "validate.pass"
}
],
"capture": {
"body": [
{
"name": "alert_id",
"value": "id"
}
]
},
"id": "LrnrTjuMQ2kCb9xZG",
"name": "Create a new Alert",
"request": {
"authentication": {},
"bodySize": -1,
"cookies": [],
"headers": [
{
"name": "Authorization",
"value": "Bearer SG.xxxxxxxx.yyyyyyyy"
}
],
"headersSize": -1,
"method": "post",
"pathParams": [],
"postData": {
"mimeType": "application/json",
"params": [],
"text": "{\n \"type\": \"stats_notification\",\n \"email_to\": \"example@example.com\",\n \"frequency\": \"daily\"\n}"
},
"queryString": [],
"transformed": false,
"url": "/alerts",
"valid": 2
}
},
{
"assertions": [
{
"expected": 200,
"location": "response.code",
"op": "eq"
},
{
"location": "response.body",
"match": 200,
"op": "validate.pass"
}
],
"capture": {},
"id": "aRd37ou83TsrHxkMc",
"name": "Retrieve a specific alert",
"request": {
"authentication": {},
"bodySize": -1,
"cookies": [],
"headers": [
{
"name": "Authorization",
"value": "Bearer SG.xxxxxxxx.yyyyyyyy"
}
],
"headersSize": -1,
"method": "get",
"pathParams": [],
"postData": {},
"queryString": [],
"transformed": false,
"url": "/alerts/{alert_id}",
"valid": 2
}
},
{
"assertions": [
{
"expected": 200,
"location": "response.code",
"op": "eq"
},
{
"location": "response.body",
"match": 200,
"op": "validate.pass"
}
],
"capture": {},
"id": "nqDiBRakuZA8nD6s7",
"name": "Update an alert",
"request": {
"authentication": {},
"bodySize": -1,
"cookies": [],
"headers": [
{
"name": "Authorization",
"value": "Bearer SG.xxxxxxxx.yyyyyyyy"
}
],
"headersSize": -1,
"method": "patch",
"pathParams": [],
"postData": {
"mimeType": "application/json",
"params": [],
"text": "{\n \"email_to\": \"matt@example.com\"\n}"
},
"queryString": [],
"transformed": false,
"url": "/alerts/{alert_id}",
"valid": 2
}
},
{
"assertions": [
{
"expected": 204,
"location": "response.code",
"op": "eq"
},
{
"location": "response.body",
"match": 204,
"op": "validate.pass"
}
],
"capture": {},
"id": "MvuQwBuikXtWbsCBt",
"name": "Delete an alert",
"request": {
"authentication": {},
"bodySize": -1,
"cookies": [],
"headers": [
{
"name": "Authorization",
"value": "Bearer SG.xxxxxxxx.yyyyyyyy"
}
],
"headersSize": -1,
"method": "delete",
"pathParams": [],
"postData": {},
"queryString": [],
"transformed": false,
"url": "/alerts/{alert_id}",
"valid": 2
}
},
{
"assertions": [
{
"expected": 400,
"location": "response.code",
"op": "eq"
},
{
"location": "response.body",
"match": 400,
"op": "validate.pass"
}
],
"capture": {},
"id": "kuh6nzo9sAfXJeSzz",
"name": "Create an alert",
"request": {
"authentication": {},
"bodySize": -1,
"cookies": [],
"headers": [
{
"name": "Authorization",
"value": "Bearer SG.xxxxxxxx.yyyyyyyy"
}
],
"headersSize": -1,
"method": "post",
"pathParams": [],
"postData": {
"mimeType": "application/json",
"params": [],
"text": "{\n \"type\": \"stats_notification\",\n \"email_to\": \"example@example.com\",\n \"frequency\": \"daily\"\n}"
},
"queryString": [],
"transformed": false,
"url": "/alerts",
"valid": 2
}
}
]
},
"HFGvnuoqkd36FarWE": {
"id": "HFGvnuoqkd36FarWE",
"initialVariables": {},
"name": "Parse Settings Test",
"steps": [
{
"assertions": [
{
"expected": 201,
"location": "response.code",
"op": "eq"
},
{
"location": "response.body",
"match": 201,
"op": "validate.pass"
}
],
"capture": {
"body": [
{
"name": "hostname",
"value": "hostname"
}
]
},
"id": "DXFjNLuibt8mBTMDE",
"name": "Create a parse setting",
"request": {
"authentication": {},
"bodySize": -1,
"cookies": [],
"headers": [
{
"name": "Authorization",
"value": "Bearer SG.xxxxxxxx.yyyyyyyy"
}
],
"headersSize": -1,
"method": "post",
"pathParams": [],
"postData": {
"mimeType": "application/json",
"params": [],
"text": "{\n \"hostname\": \"example.com\",\n \"url\": \"http://email.example.com\",\n \"spam_check\": true,\n \"send_raw\": false\n}"
},
"queryString": [],
"transformed": false,
"url": "/user/webhooks/parse/settings",
"valid": 2
}
},
{
"assertions": [
{
"expected": 200,
"location": "response.code",
"op": "eq"
},
{
"location": "response.body",
"match": 200,
"op": "validate.pass"
}
],
"capture": {},
"id": "6DoSn3KYqs3LqNzo4",
"name": "Retrieve all parse settings",
"request": {
"authentication": {},
"bodySize": -1,
"cookies": [],
"headers": [
{
"name": "Authorization",
"value": "Bearer SG.xxxxxxxx.yyyyyyyy"
}
],
"headersSize": -1,
"method": "get",
"pathParams": [],
"postData": {},
"queryString": [],
"transformed": false,
"url": "/user/webhooks/parse/settings",
"valid": 2
}
},
{
"assertions": [
{
"expected": 200,
"location": "response.code",
"op": "eq"
},
{
"location": "response.body",
"match": 200,
"op": "validate.pass"
}
],
"capture": {},
"id": "sXYsGopzxCdmtFBpf",
"name": "Retrieve a specific parse setting",
"request": {
"authentication": {},
"bodySize": -1,
"cookies": [],
"headers": [
{
"name": "Authorization",
"value": "Bearer SG.xxxxxxxx.yyyyyyyy"
}
],
"headersSize": -1,
"method": "get",
"pathParams": [],
"postData": {},
"queryString": [],
"transformed": false,
"url": "/user/webhooks/parse/settings/{hostname}",
"valid": 2
}
},
{
"assertions": [
{
"expected": 200,
"location": "response.code",
"op": "eq"
},
{
"location": "response.body",
"match": 200,
"op": "validate.pass"
}
],
"capture": {},
"id": "AL2R3B5fyfMcLKvFg",
"name": "Update a parse setting",
"request": {
"authentication": {},
"bodySize": -1,
"cookies": [],
"headers": [
{
"name": "Authorization",
"value": "Bearer SG.xxxxxxxx.yyyyyyyy"
}
],
"headersSize": -1,
"method": "patch",
"pathParams": [],
"postData": {
"mimeType": "application/json",
"params": [],
"text": "{\n \"url\": \"http://newdomain.com/parse\",\n \"spam_check\": false,\n \"send_raw\": true\n}"
},
"queryString": [],
"transformed": false,
"url": "/user/webhooks/parse/settings/{hostname}/",
"valid": 2
}
},
{
"afterScript": "function (ctx, request, response) {\n // Your javascript code here.\n // Code completion enabled.\n // You have access to a global \"SL\" object.\n // \"url\": \"http://newdomain.com/parse\",\n // \"spam_check\": false,\n // \"send_raw\": true\n var body = response.body.get();\n assert.equal(body.url, \"http://newdomain.com/parse\");\n assert.equal(body.spam_check, false);\n assert.equal(body.send_raw, true);\n}",
"assertions": [
{
"expected": 200,
"location": "response.code",
"op": "eq"
},
{
"location": "response.body",
"match": 200,
"op": "validate.pass"
}
],
"capture": {},
"id": "7ewLowiXP3fFti63Z",
"name": "Verify updating a parse setting",
"request": {
"authentication": {},
"bodySize": -1,
"cookies": [],
"headers": [
{
"name": "Authorization",
"value": "Bearer SG.xxxxxxxx.yyyyyyyy"
}
],
"headersSize": -1,
"method": "get",
"pathParams": [],
"postData": {},
"queryString": [],
"transformed": false,
"url": "/user/webhooks/parse/settings/{hostname}",
"valid": 2
}
},
{
"assertions": [
{
"expected": 204,
"location": "response.code",
"op": "eq"
},
{
"location": "response.body",
"match": 204,
"op": "validate.pass"
}
],
"capture": {},
"id": "738KySxu4mWHjDvmw",
"name": "Delete a parse setting",
"request": {
"authentication": {},
"bodySize": -1,
"cookies": [],
"headers": [
{
"name": "Authorization",
"value": "Bearer SG.xxxxxxxx.yyyyyyyy"
}
],
"headersSize": -1,
"method": "delete",
"pathParams": [],
"postData": {
"mimeType": "application/json",
"params": [],
"text": "{}"
},
"queryString": [],
"transformed": false,
"url": "/user/webhooks/parse/settings/{hostname}",
"valid": 2
}
}
]
}
}
}
kin-openapi-0.124.0/openapi3/testdata/pathref.openapi.yml 0000664 0000000 0000000 00000000313 14604223742 0023226 0 ustar 00root root 0000000 0000000 ---
openapi: 3.0.0
info:
title: 'OAI Specification in YAML'
version: 0.0.1
paths:
/test:
$ref: 'circularref.openapi.yml#/paths/~1test'
components:
schemas:
TestSchema:
type: string
kin-openapi-0.124.0/openapi3/testdata/recursiveRef/ 0000775 0000000 0000000 00000000000 14604223742 0022067 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/recursiveRef/components/ 0000775 0000000 0000000 00000000000 14604223742 0024254 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/recursiveRef/components/Bar.yml 0000664 0000000 0000000 00000000032 14604223742 0025476 0 ustar 00root root 0000000 0000000 type: string
example: bar
kin-openapi-0.124.0/openapi3/testdata/recursiveRef/components/Cat.yml 0000664 0000000 0000000 00000000121 14604223742 0025500 0 ustar 00root root 0000000 0000000 type: object
properties:
cat:
$ref: ../openapi.yml#/components/schemas/Cat
kin-openapi-0.124.0/openapi3/testdata/recursiveRef/components/Foo.yml 0000664 0000000 0000000 00000000121 14604223742 0025514 0 ustar 00root root 0000000 0000000 type: object
properties:
bar:
$ref: ../openapi.yml#/components/schemas/Bar
kin-openapi-0.124.0/openapi3/testdata/recursiveRef/components/Foo/ 0000775 0000000 0000000 00000000000 14604223742 0024777 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/recursiveRef/components/Foo/Foo2.yml 0000664 0000000 0000000 00000000124 14604223742 0026324 0 ustar 00root root 0000000 0000000 type: object
properties:
foo:
$ref: ../../openapi.yml#/components/schemas/Foo
kin-openapi-0.124.0/openapi3/testdata/recursiveRef/components/models/ 0000775 0000000 0000000 00000000000 14604223742 0025537 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/recursiveRef/components/models/error.yaml 0000664 0000000 0000000 00000000041 14604223742 0027547 0 ustar 00root root 0000000 0000000 type: object
title: ErrorDetails
kin-openapi-0.124.0/openapi3/testdata/recursiveRef/issue615.yml 0000664 0000000 0000000 00000003030 14604223742 0024172 0 ustar 00root root 0000000 0000000 openapi: "3.0.3"
info:
title: Deep recursive cyclic refs example
version: "1.0"
paths:
/foo:
$ref: ./paths/foo.yml
components:
schemas:
FilterColumnIncludes:
type: object
properties:
$includes:
$ref: '#/components/schemas/FilterPredicate'
additionalProperties: false
maxProperties: 1
minProperties: 1
FilterPredicate:
oneOf:
- $ref: '#/components/schemas/FilterValue'
- type: array
items:
$ref: '#/components/schemas/FilterPredicate'
minLength: 1
- $ref: '#/components/schemas/FilterPredicateOp'
- $ref: '#/components/schemas/FilterPredicateRangeOp'
FilterPredicateOp:
type: object
properties:
$any:
oneOf:
- type: array
items:
$ref: '#/components/schemas/FilterPredicate'
$none:
oneOf:
- $ref: '#/components/schemas/FilterPredicate'
- type: array
items:
$ref: '#/components/schemas/FilterPredicate'
additionalProperties: false
maxProperties: 1
minProperties: 1
FilterPredicateRangeOp:
type: object
properties:
$lt:
$ref: '#/components/schemas/FilterRangeValue'
additionalProperties: false
maxProperties: 2
minProperties: 2
FilterRangeValue:
oneOf:
- type: number
- type: string
FilterValue:
oneOf:
- type: number
- type: string
- type: boolean kin-openapi-0.124.0/openapi3/testdata/recursiveRef/openapi.yml 0000664 0000000 0000000 00000001332 14604223742 0024244 0 ustar 00root root 0000000 0000000 openapi: "3.0.3"
info:
title: Recursive refs example
version: "1.0"
paths:
/foo:
$ref: ./paths/foo.yml
/double-ref-foo:
get:
summary: Double ref response
description: Reference response with double reference.
responses:
"400":
$ref: "#/components/responses/400"
components:
schemas:
Foo:
$ref: ./components/Foo.yml
Foo2:
$ref: ./components/Foo/Foo2.yml
Bar:
$ref: ./components/Bar.yml
Cat:
$ref: ./components/Cat.yml
Error:
$ref: ./components/models/error.yaml
responses:
"400":
description: 400 Bad Request
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
kin-openapi-0.124.0/openapi3/testdata/recursiveRef/openapi.yml.internalized.yml 0000664 0000000 0000000 00000004305 14604223742 0027536 0 ustar 00root root 0000000 0000000 {
"components": {
"parameters": {
"number": {
"in": "query",
"name": "someNumber",
"schema": {
"type": "string"
}
}
},
"responses": {
"400": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
},
"description": "400 Bad Request"
}
},
"schemas": {
"Bar": {
"example": "bar",
"type": "string"
},
"Error":{
"title":"ErrorDetails",
"type":"object"
},
"Foo": {
"properties": {
"bar": {
"$ref": "#/components/schemas/Bar"
}
},
"type": "object"
},
"Foo2": {
"properties": {
"foo": {
"$ref": "#/components/schemas/Foo"
}
},
"type": "object"
},
"error":{
"title":"ErrorDetails",
"type":"object"
},
"Cat": {
"properties": {
"cat": {
"$ref": "#/components/schemas/Cat"
}
},
"type": "object"
}
}
},
"info": {
"title": "Recursive refs example",
"version": "1.0"
},
"openapi": "3.0.3",
"paths": {
"/double-ref-foo": {
"get": {
"description": "Reference response with double reference.",
"responses": {
"400": {
"$ref": "#/components/responses/400"
}
},
"summary": "Double ref response"
}
},
"/foo": {
"get": {
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"properties": {
"foo2": {
"$ref": "#/components/schemas/Foo2"
}
},
"type": "object"
}
}
},
"description": "OK"
},
"400": {
"$ref": "#/components/responses/400"
}
}
},
"parameters": [
{
"$ref": "#/components/parameters/number"
}
]
}
}
}
kin-openapi-0.124.0/openapi3/testdata/recursiveRef/parameters/ 0000775 0000000 0000000 00000000000 14604223742 0024232 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/recursiveRef/parameters/number.yml 0000664 0000000 0000000 00000000062 14604223742 0026243 0 ustar 00root root 0000000 0000000 name: someNumber
in: query
schema:
type: string
kin-openapi-0.124.0/openapi3/testdata/recursiveRef/paths/ 0000775 0000000 0000000 00000000000 14604223742 0023206 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/recursiveRef/paths/foo.yml 0000664 0000000 0000000 00000000542 14604223742 0024515 0 ustar 00root root 0000000 0000000 parameters:
- $ref: ../parameters/number.yml
get:
responses:
"200":
description: OK
content:
application/json:
schema:
type: object
properties:
foo2:
$ref: ../openapi.yml#/components/schemas/Foo2
"400":
$ref: "../openapi.yml#/components/responses/400"
kin-openapi-0.124.0/openapi3/testdata/refInLocalRef/ 0000775 0000000 0000000 00000000000 14604223742 0022076 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/refInLocalRef/messages/ 0000775 0000000 0000000 00000000000 14604223742 0023705 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/refInLocalRef/messages/data.json 0000664 0000000 0000000 00000000257 14604223742 0025515 0 ustar 00root root 0000000 0000000 {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int32"
},
"ref_prop_part": {
"$ref": "./dataPart.json"
}
}
}
kin-openapi-0.124.0/openapi3/testdata/refInLocalRef/messages/dataPart.json 0000664 0000000 0000000 00000000165 14604223742 0026342 0 ustar 00root root 0000000 0000000 {
"type": "object",
"properties": {
"idPart": {
"type": "integer",
"format": "int64"
}
}
}
kin-openapi-0.124.0/openapi3/testdata/refInLocalRef/messages/request.json 0000664 0000000 0000000 00000000236 14604223742 0026271 0 ustar 00root root 0000000 0000000 {
"type": "object",
"required": [
"definition_reference"
],
"properties": {
"definition_reference": {
"$ref": "./data.json"
}
}
}
kin-openapi-0.124.0/openapi3/testdata/refInLocalRef/messages/response.json 0000664 0000000 0000000 00000000161 14604223742 0026434 0 ustar 00root root 0000000 0000000 {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int32"
}
}
}
kin-openapi-0.124.0/openapi3/testdata/refInLocalRef/openapi.json 0000664 0000000 0000000 00000001676 14604223742 0024436 0 ustar 00root root 0000000 0000000 {
"openapi": "3.0.3",
"info": {
"title": "Reference in reference example",
"version": "1.0.0"
},
"paths": {
"/api/test/ref/in/ref": {
"post": {
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties" : {
"data": {
"$ref": "#/components/schemas/Request"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"$ref": "messages/response.json"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"Request": {
"$ref": "messages/request.json"
}
}
}
}
kin-openapi-0.124.0/openapi3/testdata/refInLocalRefInParentsSubdir/ 0000775 0000000 0000000 00000000000 14604223742 0025073 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/refInLocalRefInParentsSubdir/messages/ 0000775 0000000 0000000 00000000000 14604223742 0026702 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/refInLocalRefInParentsSubdir/messages/data.json 0000664 0000000 0000000 00000000257 14604223742 0030512 0 ustar 00root root 0000000 0000000 {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int32"
},
"ref_prop_part": {
"$ref": "./dataPart.json"
}
}
}
kin-openapi-0.124.0/openapi3/testdata/refInLocalRefInParentsSubdir/messages/dataPart.json 0000664 0000000 0000000 00000000165 14604223742 0031337 0 ustar 00root root 0000000 0000000 {
"type": "object",
"properties": {
"idPart": {
"type": "integer",
"format": "int64"
}
}
}
kin-openapi-0.124.0/openapi3/testdata/refInLocalRefInParentsSubdir/messages/request.json 0000664 0000000 0000000 00000000236 14604223742 0031266 0 ustar 00root root 0000000 0000000 {
"type": "object",
"required": [
"definition_reference"
],
"properties": {
"definition_reference": {
"$ref": "./data.json"
}
}
}
kin-openapi-0.124.0/openapi3/testdata/refInLocalRefInParentsSubdir/messages/response.json 0000664 0000000 0000000 00000000161 14604223742 0031431 0 ustar 00root root 0000000 0000000 {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int32"
}
}
}
kin-openapi-0.124.0/openapi3/testdata/refInLocalRefInParentsSubdir/spec/ 0000775 0000000 0000000 00000000000 14604223742 0026025 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/refInLocalRefInParentsSubdir/spec/openapi.json 0000664 0000000 0000000 00000001707 14604223742 0030360 0 ustar 00root root 0000000 0000000 {
"openapi": "3.0.3",
"info": {
"title": "Reference in reference example",
"version": "1.0.0"
},
"paths": {
"/api/test/ref/in/ref": {
"post": {
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "../messages/request.json"
}
}
}
},
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"ref_prop": {
"$ref": "#/components/schemas/Data"
}
}
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"Data": {
"$ref": "../messages/data.json"
}
}
}
}
kin-openapi-0.124.0/openapi3/testdata/refInRef/ 0000775 0000000 0000000 00000000000 14604223742 0021123 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/refInRef/messages/ 0000775 0000000 0000000 00000000000 14604223742 0022732 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/refInRef/messages/definitions.json 0000664 0000000 0000000 00000000112 14604223742 0026132 0 ustar 00root root 0000000 0000000 {
"definitions": {
"External": {
"type": "string"
}
}
}
kin-openapi-0.124.0/openapi3/testdata/refInRef/messages/request.json 0000664 0000000 0000000 00000000271 14604223742 0025315 0 ustar 00root root 0000000 0000000 {
"type": "object",
"required": [
"definition_reference"
],
"properties": {
"definition_reference": {
"$ref": "definitions.json#/definitions/External"
}
}
}
kin-openapi-0.124.0/openapi3/testdata/refInRef/messages/response.json 0000664 0000000 0000000 00000000161 14604223742 0025461 0 ustar 00root root 0000000 0000000 {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int32"
}
}
}
kin-openapi-0.124.0/openapi3/testdata/refInRef/openapi.json 0000664 0000000 0000000 00000001275 14604223742 0023456 0 ustar 00root root 0000000 0000000 {
"openapi": "3.0.3",
"info": {
"title": "Reference in reference example",
"version": "1.0.0"
},
"paths": {
"/api/test/ref/in/ref": {
"post": {
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "messages/request.json"
}
}
}
},
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"$ref": "messages/response.json"
}
}
}
}
}
}
}
}
}
kin-openapi-0.124.0/openapi3/testdata/refInRefInProperty/ 0000775 0000000 0000000 00000000000 14604223742 0023157 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/refInRefInProperty/common-data-objects/ 0000775 0000000 0000000 00000000000 14604223742 0027005 5 ustar 00root root 0000000 0000000 problem-details-0.0.1.schema.json 0000664 0000000 0000000 00000005617 14604223742 0034606 0 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/refInRefInProperty/common-data-objects {
"title": "Problem details",
"description": "Common data object for describing an error details",
"type": "object",
"additionalProperties": false,
"required": [
"type",
"title",
"status"
],
"properties": {
"type": {
"type": "string",
"description": "Unique error code",
"minLength": 1,
"example": "unauthorized",
"x-docs-examples": [
"validation-error",
"unauthorized",
"forbidden",
"internal-server-error",
"wrong-basket",
"not-found"
]
},
"title": {
"type": "string",
"description": "Human readable error message",
"minLength": 1,
"example": "Your request parameters didn't validate",
"x-docs-examples": [
"Your request parameters didn't validate",
"Requested resource is not available",
"Internal server error"
]
},
"status": {
"type": "integer",
"description": "HTTP status code",
"maximum": 599,
"minimum": 100,
"example": 200,
"x-docs-examples": [
"200",
"201",
"400",
"503"
]
},
"detail": {
"type": "string",
"description": "Human readable error description. Only for human",
"example": "Basket must have more then 1 item",
"x-docs-examples": [
"Basket must have more then 1 item"
]
},
"invalid-params": {
"type": "array",
"description": "Param list with errors",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"name",
"reason"
],
"properties": {
"name": {
"type": "string",
"description": "field name",
"minLength": 1,
"example": "age",
"x-docs-examples": [
"age",
"color"
]
},
"reason": {
"type": "string",
"description": "Field validation error text",
"minLength": 1,
"example": "must be a positive integer",
"x-docs-examples": [
"must be a positive integer",
"must be 'green', 'red' or 'blue'"
]
}
}
}
}
}
}
kin-openapi-0.124.0/openapi3/testdata/refInRefInProperty/components/ 0000775 0000000 0000000 00000000000 14604223742 0025344 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/refInRefInProperty/components/errors.yaml 0000664 0000000 0000000 00000003371 14604223742 0027550 0 ustar 00root root 0000000 0000000 components:
schemas:
Error:
type: object
description: Error info in problem-details-0.0.1 format
properties:
error:
$ref: "../common-data-objects/problem-details-0.0.1.schema.json"
responses:
400:
description: ""
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
examples:
json:
$ref: "#/components/examples/BadRequest"
401:
description: ""
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
examples:
json:
$ref: "#/components/examples/Unauthorized"
500:
description: ""
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
examples:
json:
$ref: "#/components/examples/InternalServerError"
examples:
BadRequest:
summary: Wrong format
value:
error:
type: validation-error
title: Your request parameters didn't validate.
status: 400
invalid-params:
- name: age
reason: must be a positive integer
- name: color
reason: must be 'green', 'red' or 'blue'
Unauthorized:
summary: Not authenticated
value:
error:
type: unauthorized
title: The request has not been applied because it lacks valid authentication credentials
for the target resource.
status: 401
InternalServerError:
summary: Not handled internal server error
value:
error:
type: internal-server-error
title: Internal server error.
status: 500
kin-openapi-0.124.0/openapi3/testdata/refInRefInProperty/openapi.yaml 0000664 0000000 0000000 00000001422 14604223742 0025475 0 ustar 00root root 0000000 0000000 openapi: 3.0.3
info:
title: "Reference in reference in property example"
version: "1.0.0"
paths:
/api/test/ref/in/ref/in/property:
post:
requestBody:
content:
multipart/form-data:
schema:
type: object
properties:
file:
type: array
items:
type: string
format: binary
required: true
responses:
200:
description: "Files are saved successfully"
400:
$ref: "./components/errors.yaml#/components/responses/400"
401:
$ref: "./components/errors.yaml#/components/responses/401"
500:
$ref: "./components/errors.yaml#/components/responses/500"
kin-openapi-0.124.0/openapi3/testdata/relativeDocs/ 0000775 0000000 0000000 00000000000 14604223742 0022047 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/relativeDocs/CustomTestExample.yml 0000664 0000000 0000000 00000000115 14604223742 0026215 0 ustar 00root root 0000000 0000000 summary: An example
value: |
{
"example": "hello"
} kin-openapi-0.124.0/openapi3/testdata/relativeDocs/CustomTestHeader.yml 0000664 0000000 0000000 00000000030 14604223742 0026006 0 ustar 00root root 0000000 0000000 description: description kin-openapi-0.124.0/openapi3/testdata/relativeDocs/CustomTestParameter.yml 0000664 0000000 0000000 00000000044 14604223742 0026543 0 ustar 00root root 0000000 0000000 name: param
in: path
required: true
kin-openapi-0.124.0/openapi3/testdata/relativeDocs/CustomTestPath.yml 0000664 0000000 0000000 00000000474 14604223742 0025526 0 ustar 00root root 0000000 0000000 get:
operationId: findAllDefinitions
responses:
"200":
content:
application/json:
schema:
properties:
pets:
items:
type: array
properties:
repository:
name: string
kin-openapi-0.124.0/openapi3/testdata/relativeDocs/CustomTestRequestBody.yml 0000664 0000000 0000000 00000000034 14604223742 0027070 0 ustar 00root root 0000000 0000000 description: example request kin-openapi-0.124.0/openapi3/testdata/relativeDocs/CustomTestResponse.yml 0000664 0000000 0000000 00000000030 14604223742 0026414 0 ustar 00root root 0000000 0000000 description: description kin-openapi-0.124.0/openapi3/testdata/relativeDocs/CustomTestSchema.yml 0000664 0000000 0000000 00000000014 14604223742 0026020 0 ustar 00root root 0000000 0000000 type: string kin-openapi-0.124.0/openapi3/testdata/relativeDocs/CustomTestSecurityScheme.yml 0000664 0000000 0000000 00000000030 14604223742 0027552 0 ustar 00root root 0000000 0000000 type: http
scheme: basic kin-openapi-0.124.0/openapi3/testdata/relativeDocs/openapi/ 0000775 0000000 0000000 00000000000 14604223742 0023502 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/relativeDocs/openapi/openapi.yml 0000664 0000000 0000000 00000000236 14604223742 0025661 0 ustar 00root root 0000000 0000000 openapi: 3.0.0
info:
title: ""
version: "1.0"
paths: {}
components:
responses:
TestResponse:
$ref: responses/nesteddir/CustomTestResponse.yml
kin-openapi-0.124.0/openapi3/testdata/relativeDocs/openapi/responses/ 0000775 0000000 0000000 00000000000 14604223742 0025523 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/relativeDocs/openapi/responses/custom/ 0000775 0000000 0000000 00000000000 14604223742 0027035 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/relativeDocs/openapi/responses/custom/CustomTestResponse.yml 0000664 0000000 0000000 00000000156 14604223742 0033413 0 ustar 00root root 0000000 0000000 description: description
content:
application/json:
schema:
$ref: "../../../CustomTestSchema.yml"
kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/ 0000775 0000000 0000000 00000000000 14604223742 0025040 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestExample.yml 0000664 0000000 0000000 00000000040 14604223742 0031203 0 ustar 00root root 0000000 0000000 summary: An example
value: hello kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestHeader.yml 0000664 0000000 0000000 00000000024 14604223742 0031002 0 ustar 00root root 0000000 0000000 description: header
kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestHeader1.yml 0000664 0000000 0000000 00000000025 14604223742 0031064 0 ustar 00root root 0000000 0000000 description: header1
kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestHeader1bis.yml 0000664 0000000 0000000 00000000037 14604223742 0031565 0 ustar 00root root 0000000 0000000 header:
description: header1
kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestHeader2.yml 0000664 0000000 0000000 00000000025 14604223742 0031065 0 ustar 00root root 0000000 0000000 description: header2
kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestHeader2bis.yml 0000664 0000000 0000000 00000000037 14604223742 0031566 0 ustar 00root root 0000000 0000000 header:
description: header2
kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestParameter.yml 0000664 0000000 0000000 00000000044 14604223742 0031534 0 ustar 00root root 0000000 0000000 name: param
in: path
required: true
kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestRequestBody.yml 0000664 0000000 0000000 00000000034 14604223742 0032061 0 ustar 00root root 0000000 0000000 description: example request kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestResponse.yml 0000664 0000000 0000000 00000000030 14604223742 0031405 0 ustar 00root root 0000000 0000000 description: description kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestSchema.yml 0000664 0000000 0000000 00000000014 14604223742 0031011 0 ustar 00root root 0000000 0000000 type: string kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/openapi/ 0000775 0000000 0000000 00000000000 14604223742 0026473 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/openapi/openapi.yml 0000664 0000000 0000000 00000000324 14604223742 0030650 0 ustar 00root root 0000000 0000000 openapi: 3.0.0
info:
title: ""
version: "1.0"
paths:
/pets/{id}:
$ref: "paths/nesteddir/CustomTestPath.yml"
/pets/{id}/{city}:
$ref: "paths/nesteddir/morenested/CustomTestPath.yml"
components: {}
kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/openapi/paths/ 0000775 0000000 0000000 00000000000 14604223742 0027612 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/openapi/paths/nesteddir/ 0000775 0000000 0000000 00000000000 14604223742 0031573 5 ustar 00root root 0000000 0000000 CustomTestPath.yml 0000664 0000000 0000000 00000001213 14604223742 0035163 0 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/openapi/paths/nesteddir patch:
description: "Modify a pet"
parameters:
- $ref: "../../../CustomTestParameter.yml"
requestBody:
$ref: "../../../CustomTestRequestBody.yml"
responses:
200:
description: "success"
headers:
X-Rate-Limit-Reset:
$ref: "../../../CustomTestHeader.yml"
X-Another:
$ref: ../../../CustomTestHeader1.yml
X-And-Another:
$ref: ../../../CustomTestHeader2.yml
content:
application/json:
schema:
$ref: "../../../CustomTestSchema.yml"
examples:
CustomTestExample:
$ref: "../../../CustomTestExample.yml"
morenested/ 0000775 0000000 0000000 00000000000 14604223742 0033661 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/openapi/paths/nesteddir CustomTestPath.yml 0000664 0000000 0000000 00000001272 14604223742 0037335 0 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/openapi/paths/nesteddir/morenested patch:
description: "Modify a pet"
parameters:
- $ref: "../../../../CustomTestParameter.yml"
requestBody:
$ref: "../../../../CustomTestRequestBody.yml"
responses:
200:
description: "success"
headers:
X-Rate-Limit-Reset:
$ref: "../../../../CustomTestHeader.yml"
X-Another:
$ref: '../../../../CustomTestHeader1bis.yml#/header'
X-And-Another:
$ref: '../../../../CustomTestHeader2bis.yml#/header'
content:
application/json:
schema:
$ref: "../../../../CustomTestSchema.yml"
examples:
CustomTestExample:
$ref: "../../../../CustomTestExample.yml"
kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/openapi/responses/ 0000775 0000000 0000000 00000000000 14604223742 0030514 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/openapi/responses/nesteddir/ 0000775 0000000 0000000 00000000000 14604223742 0032475 5 ustar 00root root 0000000 0000000 CustomTestResponse.yml 0000664 0000000 0000000 00000000375 14604223742 0036777 0 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/openapi/responses/nesteddir description: description
content:
application/json:
schema:
$ref: "../../../CustomTestSchema.yml"
example:
$ref: "../../../CustomTestExample.yml"
headers:
X-Rate-Limit-Reset:
$ref: "../../../CustomTestHeader.yml"
kin-openapi-0.124.0/openapi3/testdata/schema618.yml 0000664 0000000 0000000 00000002270 14604223742 0021646 0 ustar 00root root 0000000 0000000 components:
schemas:
Account:
required:
- name
- nature
type: object
properties:
id:
type: string
name:
type: string
description:
type: string
type:
type: string
enum:
- assets
- liabilities
nature:
type: string
enum:
- asset
- liability
Record:
required:
- account
- concept
type: object
properties:
account:
$ref: "#/components/schemas/Account"
concept:
type: string
partial:
type: number
credit:
type: number
debit:
type: number
JournalEntry:
required:
- type
- creationDate
- records
type: object
properties:
id:
type: string
type:
type: string
enum:
- daily
- ingress
- egress
creationDate:
type: string
format: date
records:
type: array
items:
$ref: "#/components/schemas/Record"
kin-openapi-0.124.0/openapi3/testdata/singleresponse.openapi.json 0000664 0000000 0000000 00000000074 14604223742 0025011 0 ustar 00root root 0000000 0000000 {
"description": "this is a single response definition"
}
kin-openapi-0.124.0/openapi3/testdata/singleresponse.openapi.yml 0000664 0000000 0000000 00000000066 14604223742 0024642 0 ustar 00root root 0000000 0000000 ---
description: this is a single response definition
kin-openapi-0.124.0/openapi3/testdata/spec.yaml 0000664 0000000 0000000 00000000335 14604223742 0021242 0 ustar 00root root 0000000 0000000 openapi: 3.0.1
info:
version: 1.0.0
title: Some Swagger
license:
name: MIT
paths: {}
components:
schemas:
Test:
type: object
properties:
test:
$ref: 'ext.json#/definitions/b'
kin-openapi-0.124.0/openapi3/testdata/spec.yaml.internalized.yml 0000664 0000000 0000000 00000001154 14604223742 0024531 0 ustar 00root root 0000000 0000000 {
"components": {
"schemas": {
"Test": {
"properties": {
"test": {
"$ref": "#/components/schemas/b"
}
},
"type": "object"
},
"a": {
"type": "string"
},
"b": {
"description": "I use a local reference.",
"properties": {
"name": {
"$ref": "#/components/schemas/a"
}
},
"type": "object"
}
}
},
"info": {
"license": {
"name": "MIT"
},
"title": "Some Swagger",
"version": "1.0.0"
},
"openapi": "3.0.1",
"paths": {
}
}
kin-openapi-0.124.0/openapi3/testdata/test.openapi.additionalproperties.yml 0000664 0000000 0000000 00000000472 14604223742 0027006 0 ustar 00root root 0000000 0000000 openapi: 3.0.0
info:
title: "OAI Specification in YAML"
version: 0.0.1
paths: {}
components:
schemas:
TestSchema:
type: object
additionalProperties:
type: array
items:
type: string
TestSchema2:
type: object
additionalProperties:
type: string
kin-openapi-0.124.0/openapi3/testdata/test.openapi.json 0000664 0000000 0000000 00000000366 14604223742 0022734 0 ustar 00root root 0000000 0000000 {
"openapi": "3.0.0",
"info": {
"title": "",
"version": "0.0.1"
},
"paths": {},
"components": {
"schemas": {
"TestSchema": {
"type": "string"
}
}
}
}
kin-openapi-0.124.0/openapi3/testdata/test.openapi.yml 0000664 0000000 0000000 00000000223 14604223742 0022554 0 ustar 00root root 0000000 0000000 ---
openapi: 3.0.0
info:
title: 'OAI Specification in YAML'
version: 0.0.1
paths: {}
components:
schemas:
TestSchema:
type: string
kin-openapi-0.124.0/openapi3/testdata/testpath.yaml 0000664 0000000 0000000 00000000452 14604223742 0022144 0 ustar 00root root 0000000 0000000 paths:
/testpath:
get:
responses:
"200":
$ref: "#/components/responses/testpath_200_response"
components:
responses:
testpath_200_response:
description: a custom response
content:
application/json:
schema:
type: string
kin-openapi-0.124.0/openapi3/testdata/testref.openapi.json 0000664 0000000 0000000 00000000564 14604223742 0023431 0 ustar 00root root 0000000 0000000 {
"openapi": "3.0.0",
"info": {
"title": "OAI Specification w/ refs in JSON",
"x-my-extension": {"k": 42},
"version": "1"
},
"paths": {},
"components": {
"schemas": {
"AnotherTestSchema": {
"$ref": "components.openapi.json#/components/schemas/CustomTestSchema"
}
}
}
} kin-openapi-0.124.0/openapi3/testdata/testref.openapi.yml 0000664 0000000 0000000 00000000364 14604223742 0023257 0 ustar 00root root 0000000 0000000 ---
openapi: 3.0.0
info:
title: 'OAI Specification w/ refs in YAML'
# x-my-extension: {k: 42},
version: '1'
paths: {}
components:
schemas:
AnotherTestSchema:
$ref: 'components.openapi.yml#/components/schemas/CustomTestSchema'
kin-openapi-0.124.0/openapi3/testdata/testref.openapi.yml.internalized.yml 0000664 0000000 0000000 00000000453 14604223742 0026545 0 ustar 00root root 0000000 0000000 {
"components": {
"schemas": {
"AnotherTestSchema": {
"type": "string"
},
"CustomTestSchema": {
"type": "string"
}
}
},
"info": {
"title": "OAI Specification w/ refs in YAML",
"version": "1"
},
"openapi": "3.0.0",
"paths": {
}
}
kin-openapi-0.124.0/openapi3/testdata/testrefsinglecomponent.openapi.json 0000664 0000000 0000000 00000000413 14604223742 0026547 0 ustar 00root root 0000000 0000000 {
"openapi": "3.0.0",
"info": {
"title": "",
"version": "1"
},
"paths": {},
"components": {
"responses": {
"SomeResponse": {
"$ref": "singleresponse.openapi.json"
}
}
}
}
kin-openapi-0.124.0/openapi3/testdata/testrefsinglecomponent.openapi.yml 0000664 0000000 0000000 00000000263 14604223742 0026402 0 ustar 00root root 0000000 0000000 ---
openapi: 3.0.0
info:
title: 'OAI Specification w/ refs in YAML'
version: '1'
paths: {}
components:
responses:
SomeResponse:
"$ref": singleresponse.openapi.yml
kin-openapi-0.124.0/openapi3/unique_items_checker_test.go 0000664 0000000 0000000 00000001663 14604223742 0023401 0 ustar 00root root 0000000 0000000 package openapi3_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
)
func TestRegisterArrayUniqueItemsChecker(t *testing.T) {
var (
schema = openapi3.Schema{
Type: &openapi3.Types{"array"},
UniqueItems: true,
Items: openapi3.NewStringSchema().NewRef(),
}
val = []interface{}{"1", "2", "3"}
)
// Fist checked by predefined function
err := schema.VisitJSON(val)
require.NoError(t, err)
// Register a function will always return false when check if a
// slice has unique items, then use a slice indeed has unique
// items to verify that check unique items will failed.
openapi3.RegisterArrayUniqueItemsChecker(func(items []interface{}) bool {
return false
})
defer openapi3.RegisterArrayUniqueItemsChecker(nil) // Reset for other tests
err = schema.VisitJSON(val)
require.Error(t, err)
require.ErrorContains(t, err, "duplicate items found")
}
kin-openapi-0.124.0/openapi3/validation_issue409_test.go 0000664 0000000 0000000 00000002277 14604223742 0023007 0 ustar 00root root 0000000 0000000 package openapi3_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
)
func TestIssue409PatternIgnored(t *testing.T) {
l := openapi3.NewLoader()
s, err := l.LoadFromFile("testdata/issue409.yml")
require.NoError(t, err)
err = s.Validate(l.Context, openapi3.DisableSchemaPatternValidation())
assert.NoError(t, err)
}
func TestIssue409PatternNotIgnored(t *testing.T) {
l := openapi3.NewLoader()
s, err := l.LoadFromFile("testdata/issue409.yml")
require.NoError(t, err)
err = s.Validate(l.Context)
assert.Error(t, err)
}
func TestIssue409HygienicUseOfCtx(t *testing.T) {
l := openapi3.NewLoader()
doc, err := l.LoadFromFile("testdata/issue409.yml")
require.NoError(t, err)
err = doc.Validate(l.Context, openapi3.DisableSchemaPatternValidation())
assert.NoError(t, err)
err = doc.Validate(l.Context)
assert.Error(t, err)
// and the other way
l = openapi3.NewLoader()
doc, err = l.LoadFromFile("testdata/issue409.yml")
require.NoError(t, err)
err = doc.Validate(l.Context)
assert.Error(t, err)
err = doc.Validate(l.Context, openapi3.DisableSchemaPatternValidation())
assert.NoError(t, err)
}
kin-openapi-0.124.0/openapi3/validation_options.go 0000664 0000000 0000000 00000010156 14604223742 0022051 0 ustar 00root root 0000000 0000000 package openapi3
import "context"
// ValidationOption allows the modification of how the OpenAPI document is validated.
type ValidationOption func(options *ValidationOptions)
// ValidationOptions provides configuration for validating OpenAPI documents.
type ValidationOptions struct {
examplesValidationAsReq, examplesValidationAsRes bool
examplesValidationDisabled bool
schemaDefaultsValidationDisabled bool
schemaFormatValidationEnabled bool
schemaPatternValidationDisabled bool
extraSiblingFieldsAllowed map[string]struct{}
}
type validationOptionsKey struct{}
// AllowExtraSiblingFields called as AllowExtraSiblingFields("description") makes Validate not return an error when said field appears next to a $ref.
func AllowExtraSiblingFields(fields ...string) ValidationOption {
return func(options *ValidationOptions) {
if options.extraSiblingFieldsAllowed == nil && len(fields) != 0 {
options.extraSiblingFieldsAllowed = make(map[string]struct{}, len(fields))
}
for _, field := range fields {
options.extraSiblingFieldsAllowed[field] = struct{}{}
}
}
}
// EnableSchemaFormatValidation makes Validate not return an error when validating documents that mention schema formats that are not defined by the OpenAPIv3 specification.
// By default, schema format validation is disabled.
func EnableSchemaFormatValidation() ValidationOption {
return func(options *ValidationOptions) {
options.schemaFormatValidationEnabled = true
}
}
// DisableSchemaFormatValidation does the opposite of EnableSchemaFormatValidation.
// By default, schema format validation is disabled.
func DisableSchemaFormatValidation() ValidationOption {
return func(options *ValidationOptions) {
options.schemaFormatValidationEnabled = false
}
}
// EnableSchemaPatternValidation does the opposite of DisableSchemaPatternValidation.
// By default, schema pattern validation is enabled.
func EnableSchemaPatternValidation() ValidationOption {
return func(options *ValidationOptions) {
options.schemaPatternValidationDisabled = false
}
}
// DisableSchemaPatternValidation makes Validate not return an error when validating patterns that are not supported by the Go regexp engine.
func DisableSchemaPatternValidation() ValidationOption {
return func(options *ValidationOptions) {
options.schemaPatternValidationDisabled = true
}
}
// EnableSchemaDefaultsValidation does the opposite of DisableSchemaDefaultsValidation.
// By default, schema default values are validated against their schema.
func EnableSchemaDefaultsValidation() ValidationOption {
return func(options *ValidationOptions) {
options.schemaDefaultsValidationDisabled = false
}
}
// DisableSchemaDefaultsValidation disables schemas' default field validation.
// By default, schema default values are validated against their schema.
func DisableSchemaDefaultsValidation() ValidationOption {
return func(options *ValidationOptions) {
options.schemaDefaultsValidationDisabled = true
}
}
// EnableExamplesValidation does the opposite of DisableExamplesValidation.
// By default, all schema examples are validated.
func EnableExamplesValidation() ValidationOption {
return func(options *ValidationOptions) {
options.examplesValidationDisabled = false
}
}
// DisableExamplesValidation disables all example schema validation.
// By default, all schema examples are validated.
func DisableExamplesValidation() ValidationOption {
return func(options *ValidationOptions) {
options.examplesValidationDisabled = true
}
}
// WithValidationOptions allows adding validation options to a context object that can be used when validating any OpenAPI type.
func WithValidationOptions(ctx context.Context, opts ...ValidationOption) context.Context {
if len(opts) == 0 {
return ctx
}
options := &ValidationOptions{}
for _, opt := range opts {
opt(options)
}
return context.WithValue(ctx, validationOptionsKey{}, options)
}
func getValidationOptions(ctx context.Context) *ValidationOptions {
if options, ok := ctx.Value(validationOptionsKey{}).(*ValidationOptions); ok {
return options
}
return &ValidationOptions{}
}
kin-openapi-0.124.0/openapi3/visited.go 0000664 0000000 0000000 00000001663 14604223742 0017616 0 ustar 00root root 0000000 0000000 package openapi3
func newVisited() visitedComponent {
return visitedComponent{
header: make(map[*Header]struct{}),
schema: make(map[*Schema]struct{}),
}
}
type visitedComponent struct {
header map[*Header]struct{}
schema map[*Schema]struct{}
}
// resetVisited clears visitedComponent map
// should be called before recursion over doc *T
func (doc *T) resetVisited() {
doc.visited = newVisited()
}
// isVisitedHeader returns `true` if the *Header pointer was already visited
// otherwise it returns `false`
func (doc *T) isVisitedHeader(h *Header) bool {
if _, ok := doc.visited.header[h]; ok {
return true
}
doc.visited.header[h] = struct{}{}
return false
}
// isVisitedHeader returns `true` if the *Schema pointer was already visited
// otherwise it returns `false`
func (doc *T) isVisitedSchema(s *Schema) bool {
if _, ok := doc.visited.schema[s]; ok {
return true
}
doc.visited.schema[s] = struct{}{}
return false
}
kin-openapi-0.124.0/openapi3/xml.go 0000664 0000000 0000000 00000003517 14604223742 0016747 0 ustar 00root root 0000000 0000000 package openapi3
import (
"context"
"encoding/json"
)
// XML is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#xml-object
type XML struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"`
Attribute bool `json:"attribute,omitempty" yaml:"attribute,omitempty"`
Wrapped bool `json:"wrapped,omitempty" yaml:"wrapped,omitempty"`
}
// MarshalJSON returns the JSON encoding of XML.
func (xml XML) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{}, 5+len(xml.Extensions))
for k, v := range xml.Extensions {
m[k] = v
}
if x := xml.Name; x != "" {
m["name"] = x
}
if x := xml.Namespace; x != "" {
m["namespace"] = x
}
if x := xml.Prefix; x != "" {
m["prefix"] = x
}
if x := xml.Attribute; x {
m["attribute"] = x
}
if x := xml.Wrapped; x {
m["wrapped"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets XML to a copy of data.
func (xml *XML) UnmarshalJSON(data []byte) error {
type XMLBis XML
var x XMLBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "name")
delete(x.Extensions, "namespace")
delete(x.Extensions, "prefix")
delete(x.Extensions, "attribute")
delete(x.Extensions, "wrapped")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*xml = XML(x)
return nil
}
// Validate returns an error if XML does not comply with the OpenAPI spec.
func (xml *XML) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
return validateExtensions(ctx, xml.Extensions)
}
kin-openapi-0.124.0/openapi3filter/ 0000775 0000000 0000000 00000000000 14604223742 0017020 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3filter/authentication_input.go 0000664 0000000 0000000 00000001314 14604223742 0023604 0 ustar 00root root 0000000 0000000 package openapi3filter
import (
"fmt"
"github.com/getkin/kin-openapi/openapi3"
)
type AuthenticationInput struct {
RequestValidationInput *RequestValidationInput
SecuritySchemeName string
SecurityScheme *openapi3.SecurityScheme
Scopes []string
}
func (input *AuthenticationInput) NewError(err error) error {
if err == nil {
if len(input.Scopes) == 0 {
err = fmt.Errorf("security requirement %q failed", input.SecuritySchemeName)
} else {
err = fmt.Errorf("security requirement %q (scopes: %+v) failed", input.SecuritySchemeName, input.Scopes)
}
}
return &RequestError{
Input: input.RequestValidationInput,
Reason: "authorization failed",
Err: err,
}
}
kin-openapi-0.124.0/openapi3filter/csv_file_upload_test.go 0000664 0000000 0000000 00000004477 14604223742 0023560 0 ustar 00root root 0000000 0000000 package openapi3filter_test
import (
"bytes"
"context"
"io"
"mime/multipart"
"net/http"
"net/textproto"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/openapi3filter"
"github.com/getkin/kin-openapi/routers/gorillamux"
)
func TestValidateCsvFileUpload(t *testing.T) {
const spec = `
openapi: 3.0.0
info:
title: 'Validator'
version: 0.0.1
paths:
/test:
post:
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required:
- file
properties:
file:
type: string
format: string
responses:
'200':
description: Created
`
loader := openapi3.NewLoader()
doc, err := loader.LoadFromData([]byte(spec))
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
router, err := gorillamux.NewRouter(doc)
require.NoError(t, err)
tests := []struct {
csvData string
wantErr bool
}{
{
`foo,bar`,
false,
},
{
`"foo","bar"`,
false,
},
{
`foo,bar
baz,qux`,
false,
},
{
`foo,bar
baz,qux,quux`,
true,
},
{
`"""`,
true,
},
}
for _, tt := range tests {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
{ // Add file data
h := make(textproto.MIMEHeader)
h.Set("Content-Disposition", `form-data; name="file"; filename="hello.csv"`)
h.Set("Content-Type", "text/csv")
fw, err := writer.CreatePart(h)
require.NoError(t, err)
_, err = io.Copy(fw, strings.NewReader(tt.csvData))
require.NoError(t, err)
}
writer.Close()
req, err := http.NewRequest(http.MethodPost, "/test", bytes.NewReader(body.Bytes()))
require.NoError(t, err)
req.Header.Set("Content-Type", writer.FormDataContentType())
route, pathParams, err := router.FindRoute(req)
require.NoError(t, err)
if err = openapi3filter.ValidateRequestBody(
context.Background(),
&openapi3filter.RequestValidationInput{
Request: req,
PathParams: pathParams,
Route: route,
},
route.Operation.RequestBody.Value,
); err != nil {
if !tt.wantErr {
t.Errorf("got %v", err)
}
continue
}
if tt.wantErr {
t.Errorf("want err")
}
}
}
kin-openapi-0.124.0/openapi3filter/errors.go 0000664 0000000 0000000 00000004216 14604223742 0020666 0 ustar 00root root 0000000 0000000 package openapi3filter
import (
"bytes"
"fmt"
"github.com/getkin/kin-openapi/openapi3"
)
var _ error = &RequestError{}
// RequestError is returned by ValidateRequest when request does not match OpenAPI spec
type RequestError struct {
Input *RequestValidationInput
Parameter *openapi3.Parameter
RequestBody *openapi3.RequestBody
Reason string
Err error
}
var _ interface{ Unwrap() error } = RequestError{}
func (err *RequestError) Error() string {
reason := err.Reason
if e := err.Err; e != nil {
if len(reason) == 0 || reason == e.Error() {
reason = e.Error()
} else {
reason += ": " + e.Error()
}
}
if v := err.Parameter; v != nil {
return fmt.Sprintf("parameter %q in %s has an error: %s", v.Name, v.In, reason)
} else if v := err.RequestBody; v != nil {
return fmt.Sprintf("request body has an error: %s", reason)
} else {
return reason
}
}
func (err RequestError) Unwrap() error {
return err.Err
}
var _ error = &ResponseError{}
// ResponseError is returned by ValidateResponse when response does not match OpenAPI spec
type ResponseError struct {
Input *ResponseValidationInput
Reason string
Err error
}
var _ interface{ Unwrap() error } = ResponseError{}
func (err *ResponseError) Error() string {
reason := err.Reason
if e := err.Err; e != nil {
if len(reason) == 0 {
reason = e.Error()
} else {
reason += ": " + e.Error()
}
}
return reason
}
func (err ResponseError) Unwrap() error {
return err.Err
}
var _ error = &SecurityRequirementsError{}
// SecurityRequirementsError is returned by ValidateSecurityRequirements
// when no requirement is met.
type SecurityRequirementsError struct {
SecurityRequirements openapi3.SecurityRequirements
Errors []error
}
func (err *SecurityRequirementsError) Error() string {
buff := bytes.NewBufferString("security requirements failed: ")
for i, e := range err.Errors {
buff.WriteString(e.Error())
if i != len(err.Errors)-1 {
buff.WriteString(" | ")
}
}
return buff.String()
}
var _ interface{ Unwrap() []error } = SecurityRequirementsError{}
func (err SecurityRequirementsError) Unwrap() []error {
return err.Errors
}
kin-openapi-0.124.0/openapi3filter/internal.go 0000664 0000000 0000000 00000000720 14604223742 0021162 0 ustar 00root root 0000000 0000000 package openapi3filter
import (
"reflect"
"strings"
)
func parseMediaType(contentType string) string {
i := strings.IndexByte(contentType, ';')
if i < 0 {
return contentType
}
return contentType[:i]
}
func isNilValue(value interface{}) bool {
if value == nil {
return true
}
switch reflect.TypeOf(value).Kind() {
case reflect.Ptr, reflect.Map, reflect.Array, reflect.Chan, reflect.Slice:
return reflect.ValueOf(value).IsNil()
}
return false
}
kin-openapi-0.124.0/openapi3filter/issue201_test.go 0000664 0000000 0000000 00000006012 14604223742 0021760 0 ustar 00root root 0000000 0000000 package openapi3filter
import (
"context"
"io"
"net/http"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/routers/gorillamux"
)
func TestIssue201(t *testing.T) {
loader := openapi3.NewLoader()
ctx := loader.Context
spec := `
openapi: '3.0.3'
info:
version: 1.0.0
title: Sample API
paths:
/_:
get:
description: ''
responses:
default:
description: ''
content:
application/json:
schema:
type: object
headers:
X-Blip:
description: ''
required: true
schema:
type: string
pattern: '^blip$'
x-blop:
description: ''
schema:
type: string
pattern: '^blop$'
X-Blap:
description: ''
required: true
schema:
type: string
pattern: '^blap$'
X-Blup:
description: ''
required: true
schema:
type: string
pattern: '^blup$'
`[1:]
doc, err := loader.LoadFromData([]byte(spec))
require.NoError(t, err)
err = doc.Validate(ctx)
require.NoError(t, err)
for name, testcase := range map[string]struct {
headers map[string]string
err string
}{
"no error": {
headers: map[string]string{
"X-Blip": "blip",
"x-blop": "blop",
"X-Blap": "blap",
"X-Blup": "blup",
},
},
"missing non-required header": {
headers: map[string]string{
"X-Blip": "blip",
// "x-blop": "blop",
"X-Blap": "blap",
"X-Blup": "blup",
},
},
"missing required header": {
err: `response header "X-Blip" missing`,
headers: map[string]string{
// "X-Blip": "blip",
"x-blop": "blop",
"X-Blap": "blap",
"X-Blup": "blup",
},
},
"invalid required header": {
err: `response header "X-Blup" doesn't match schema: string doesn't match the regular expression "^blup$"`,
headers: map[string]string{
"X-Blip": "blip",
"x-blop": "blop",
"X-Blap": "blap",
"X-Blup": "bluuuuuup",
},
},
} {
t.Run(name, func(t *testing.T) {
router, err := gorillamux.NewRouter(doc)
require.NoError(t, err)
r, err := http.NewRequest(http.MethodGet, `/_`, nil)
require.NoError(t, err)
r.Header.Add(headerCT, "application/json")
for k, v := range testcase.headers {
r.Header.Add(k, v)
}
route, pathParams, err := router.FindRoute(r)
require.NoError(t, err)
err = ValidateResponse(context.Background(), &ResponseValidationInput{
RequestValidationInput: &RequestValidationInput{
Request: r,
PathParams: pathParams,
Route: route,
},
Status: 200,
Header: r.Header,
Body: io.NopCloser(strings.NewReader(`{}`)),
})
if e := testcase.err; e != "" {
require.ErrorContains(t, err, e)
} else {
require.NoError(t, err)
}
})
}
}
kin-openapi-0.124.0/openapi3filter/issue267_test.go 0000664 0000000 0000000 00000020463 14604223742 0022002 0 ustar 00root root 0000000 0000000 package openapi3filter
import (
"net/http"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/routers/gorillamux"
)
func TestIssue267(t *testing.T) {
spec := `
openapi: 3.0.0
info:
description: This is a sample of the API
version: 1.0.0
title: sample API
tags:
- name: authorization
description: Create and validate authorization tokens using oauth
paths:
/oauth2/token:
post:
tags:
- authorization
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/AccessTokenRequest'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/AccessTokenRequest'
responses:
'200':
description: 'The request was successful and a token was issued.'
/oauth2/any-token:
post:
tags:
- authorization
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/AnyTokenRequest'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/AnyTokenRequest'
responses:
'200':
description: 'Any type of token request was successful.'
/oauth2/all-token:
post:
tags:
- authorization
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/AllTokenRequest'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/AllTokenRequest'
responses:
'200':
description: 'All type of token request was successful.'
components:
schemas:
AccessTokenRequest:
description: 'Describes all of the potential access token requests that can be received'
type: object
oneOf:
- $ref: '#/components/schemas/ClientCredentialsTokenRequest'
- $ref: '#/components/schemas/RefreshTokenRequest'
ClientCredentialsTokenRequest:
description: 'The client_id and client_secret properties should only be sent in form data if the client does not support basic authentication for sending client credentials.'
type: object
properties:
grant_type:
type: string
enum:
- client_credentials
scope:
type: string
client_id:
type: string
client_secret:
type: string
required:
- grant_type
- scope
- client_id
- client_secret
RefreshTokenRequest:
type: object
properties:
grant_type:
type: string
enum:
- refresh_token
client_id:
type: string
refresh_token:
type: string
required:
- grant_type
- client_id
- refresh_token
AnyTokenRequest:
type: object
anyOf:
- $ref: '#/components/schemas/ClientCredentialsTokenRequest'
- $ref: '#/components/schemas/RefreshTokenRequest'
- $ref: '#/components/schemas/AdditionalTokenRequest'
AdditionalTokenRequest:
type: object
properties:
grant_type:
type: string
enum:
- additional_grant
additional_info:
type: string
required:
- grant_type
- additional_info
AllTokenRequest:
type: object
allOf:
- $ref: '#/components/schemas/ClientCredentialsTokenRequest'
- $ref: '#/components/schemas/TrackingInfo'
TrackingInfo:
type: object
properties:
tracking_id:
type: string
required:
- tracking_id
`[1:]
loader := openapi3.NewLoader()
doc, err := loader.LoadFromData([]byte(spec))
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
router, err := gorillamux.NewRouter(doc)
require.NoError(t, err)
for _, testcase := range []struct {
endpoint string
ct string
data string
shouldFail bool
}{
// application/json
{
endpoint: "/oauth2/token",
ct: "application/json",
data: `{"grant_type":"client_credentials", "scope":"testscope", "client_id":"myclient", "client_secret":"mypass"}`,
shouldFail: false,
},
{
endpoint: "/oauth2/token",
ct: "application/json",
data: `{"grant_type":"client_credentials", "scope":"testscope", "client_id":"myclient", "client_secret":"mypass","request":1}`,
shouldFail: false,
},
{
endpoint: "/oauth2/any-token",
ct: "application/json",
data: `{"grant_type":"client_credentials", "scope":"testscope", "client_id":"myclient", "client_secret":"mypass"}`,
shouldFail: false,
},
{
endpoint: "/oauth2/any-token",
ct: "application/json",
data: `{"grant_type":"refresh_token", "client_id":"myclient", "refresh_token":"someRefreshToken"}`,
shouldFail: false,
},
{
endpoint: "/oauth2/any-token",
ct: "application/json",
data: `{"grant_type":"additional_grant", "additional_info":"extraInfo"}`,
shouldFail: false,
},
{
endpoint: "/oauth2/any-token",
ct: "application/json",
data: `{"grant_type":"invalid_grant", "extra_field":"extraValue"}`,
shouldFail: true,
},
{
endpoint: "/oauth2/all-token",
ct: "application/json",
data: `{
"grant_type": "client_credentials",
"scope": "testscope",
"client_id": "myclient",
"client_secret": "mypass",
"tracking_id": "123456"
}`,
shouldFail: false,
},
{
endpoint: "/oauth2/all-token",
ct: "application/json",
data: `{"grant_type":"invalid", "client_id":"myclient", "extra_field":"extraValue"}`,
shouldFail: true,
},
// application/x-www-form-urlencoded
{
endpoint: "/oauth2/token",
ct: "application/x-www-form-urlencoded",
data: "grant_type=client_credentials&scope=testscope&client_id=myclient&client_secret=mypass",
shouldFail: false,
},
{
endpoint: "/oauth2/token",
ct: "application/x-www-form-urlencoded",
data: "grant_type=client_credentials&scope=testscope&client_id=myclient&client_secret=mypass&request=1",
shouldFail: false,
},
{
endpoint: "/oauth2/token",
ct: "application/x-www-form-urlencoded",
data: "invalid_field=invalid_value",
shouldFail: true,
},
{
endpoint: "/oauth2/any-token",
ct: "application/x-www-form-urlencoded",
data: "grant_type=client_credentials&scope=testscope&client_id=myclient&client_secret=mypass",
shouldFail: false,
},
{
endpoint: "/oauth2/any-token",
ct: "application/x-www-form-urlencoded",
data: "grant_type=refresh_token&client_id=myclient&refresh_token=someRefreshToken",
shouldFail: false,
},
{
endpoint: "/oauth2/any-token",
ct: "application/x-www-form-urlencoded",
data: "grant_type=additional_grant&additional_info=extraInfo",
shouldFail: false,
},
{
endpoint: "/oauth2/any-token",
ct: "application/x-www-form-urlencoded",
data: "grant_type=invalid_grant&extra_field=extraValue",
shouldFail: true,
},
{
endpoint: "/oauth2/all-token",
ct: "application/x-www-form-urlencoded",
data: "grant_type=client_credentials&scope=testscope&client_id=myclient&client_secret=mypass&tracking_id=123456",
shouldFail: false,
},
{
endpoint: "/oauth2/all-token",
ct: "application/x-www-form-urlencoded",
data: "grant_type=invalid&client_id=myclient&extra_field=extraValue",
shouldFail: true,
},
} {
t.Run(testcase.ct, func(t *testing.T) {
data := strings.NewReader(testcase.data)
req, err := http.NewRequest("POST", testcase.endpoint, data)
require.NoError(t, err)
req.Header.Add("Content-Type", testcase.ct)
route, pathParams, err := router.FindRoute(req)
require.NoError(t, err)
validationInput := &RequestValidationInput{
Request: req,
PathParams: pathParams,
Route: route,
}
err = ValidateRequest(loader.Context, validationInput)
if testcase.shouldFail {
require.Error(t, err, "This test case should fail "+testcase.data)
} else {
require.NoError(t, err, "This test case should pass "+testcase.data)
}
})
}
}
kin-openapi-0.124.0/openapi3filter/issue436_test.go 0000664 0000000 0000000 00000005456 14604223742 0022005 0 ustar 00root root 0000000 0000000 package openapi3filter_test
import (
"bytes"
"context"
"io"
"mime/multipart"
"net/http"
"net/textproto"
"strings"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/openapi3filter"
"github.com/getkin/kin-openapi/routers/gorillamux"
)
func Example_validateMultipartFormData() {
const spec = `
openapi: 3.0.0
info:
title: 'Validator'
version: 0.0.1
paths:
/test:
post:
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required:
- file
properties:
file:
type: string
format: binary
categories:
type: array
items:
$ref: "#/components/schemas/Category"
responses:
'200':
description: Created
components:
schemas:
Category:
type: object
properties:
name:
type: string
required:
- name
`
loader := openapi3.NewLoader()
doc, err := loader.LoadFromData([]byte(spec))
if err != nil {
panic(err)
}
if err = doc.Validate(loader.Context); err != nil {
panic(err)
}
router, err := gorillamux.NewRouter(doc)
if err != nil {
panic(err)
}
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
{ // Add a single "categories" item as part data
h := make(textproto.MIMEHeader)
h.Set("Content-Disposition", `form-data; name="categories"`)
h.Set("Content-Type", "application/json")
fw, err := writer.CreatePart(h)
if err != nil {
panic(err)
}
if _, err = io.Copy(fw, strings.NewReader(`{"name": "foo"}`)); err != nil {
panic(err)
}
}
{ // Add a single "categories" item as part data, again
h := make(textproto.MIMEHeader)
h.Set("Content-Disposition", `form-data; name="categories"`)
h.Set("Content-Type", "application/json")
fw, err := writer.CreatePart(h)
if err != nil {
panic(err)
}
if _, err = io.Copy(fw, strings.NewReader(`{"name": "bar"}`)); err != nil {
panic(err)
}
}
{ // Add file data
fw, err := writer.CreateFormFile("file", "hello.txt")
if err != nil {
panic(err)
}
if _, err = io.Copy(fw, strings.NewReader("hello")); err != nil {
panic(err)
}
}
writer.Close()
req, err := http.NewRequest(http.MethodPost, "/test", bytes.NewReader(body.Bytes()))
if err != nil {
panic(err)
}
req.Header.Set("Content-Type", writer.FormDataContentType())
route, pathParams, err := router.FindRoute(req)
if err != nil {
panic(err)
}
if err = openapi3filter.ValidateRequestBody(
context.Background(),
&openapi3filter.RequestValidationInput{
Request: req,
PathParams: pathParams,
Route: route,
},
route.Operation.RequestBody.Value,
); err != nil {
panic(err)
}
// Output:
}
kin-openapi-0.124.0/openapi3filter/issue624_test.go 0000664 0000000 0000000 00000002751 14604223742 0021777 0 ustar 00root root 0000000 0000000 package openapi3filter
import (
"net/http"
"testing"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/routers/gorillamux"
)
func TestIssue624(t *testing.T) {
loader := openapi3.NewLoader()
ctx := loader.Context
spec := `
openapi: 3.0.0
info:
version: 1.0.0
title: Sample API
paths:
/items:
get:
description: Returns a list of stuff
parameters:
- description: "test non object"
explode: true
style: form
in: query
name: test
required: false
content:
application/json:
schema:
anyOf:
- type: string
- type: integer
responses:
'200':
description: Successful response
`[1:]
doc, err := loader.LoadFromData([]byte(spec))
require.NoError(t, err)
err = doc.Validate(ctx)
require.NoError(t, err)
router, err := gorillamux.NewRouter(doc)
require.NoError(t, err)
for _, testcase := range []string{`test1`, `test[1`} {
t.Run(testcase, func(t *testing.T) {
httpReq, err := http.NewRequest(http.MethodGet, `/items?test=`+testcase, nil)
require.NoError(t, err)
route, pathParams, err := router.FindRoute(httpReq)
require.NoError(t, err)
requestValidationInput := &RequestValidationInput{
Request: httpReq,
PathParams: pathParams,
Route: route,
}
err = ValidateRequest(ctx, requestValidationInput)
require.NoError(t, err)
})
}
}
kin-openapi-0.124.0/openapi3filter/issue625_test.go 0000664 0000000 0000000 00000005517 14604223742 0022003 0 ustar 00root root 0000000 0000000 package openapi3filter_test
import (
"net/http"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/openapi3filter"
"github.com/getkin/kin-openapi/routers/gorillamux"
)
func TestIssue625(t *testing.T) {
anyOfArraySpec := `
openapi: 3.0.0
info:
version: 1.0.0
title: Sample API
paths:
/items:
get:
description: Returns a list of stuff
parameters:
- description: test object
explode: false
in: query
name: test
required: false
schema:
type: array
items:
anyOf:
- type: integer
- type: boolean
responses:
'200':
description: Successful response
`[1:]
oneOfArraySpec := strings.ReplaceAll(anyOfArraySpec, "anyOf", "oneOf")
allOfArraySpec := strings.ReplaceAll(strings.ReplaceAll(anyOfArraySpec, "anyOf", "allOf"),
"type: boolean", "type: number")
tests := []struct {
name string
spec string
req string
errStr string
}{
{
name: "success anyof object array",
spec: anyOfArraySpec,
req: "/items?test=3,7",
},
{
name: "failed anyof object array",
spec: anyOfArraySpec,
req: "/items?test=s1,s2",
errStr: `parameter "test" in query has an error: path 0: value s1: an invalid boolean: invalid syntax`,
},
{
name: "success allof object array",
spec: allOfArraySpec,
req: `/items?test=1,3`,
},
{
name: "failed allof object array",
spec: allOfArraySpec,
req: `/items?test=1.2,3.1`,
errStr: `parameter "test" in query has an error: path 0: value 1.2: an invalid integer: invalid syntax`,
},
{
name: "success oneof object array",
spec: oneOfArraySpec,
req: `/items?test=true,3`,
},
{
name: "failed oneof object array",
spec: oneOfArraySpec,
req: `/items?test="val1","val2"`,
errStr: `parameter "test" in query has an error: item 0: decoding oneOf failed: 0 schemas matched`,
},
}
for _, testcase := range tests {
t.Run(testcase.name, func(t *testing.T) {
loader := openapi3.NewLoader()
ctx := loader.Context
doc, err := loader.LoadFromData([]byte(testcase.spec))
require.NoError(t, err)
err = doc.Validate(ctx)
require.NoError(t, err)
router, err := gorillamux.NewRouter(doc)
require.NoError(t, err)
httpReq, err := http.NewRequest(http.MethodGet, testcase.req, nil)
require.NoError(t, err)
route, pathParams, err := router.FindRoute(httpReq)
require.NoError(t, err)
requestValidationInput := &openapi3filter.RequestValidationInput{
Request: httpReq,
PathParams: pathParams,
Route: route,
}
err = openapi3filter.ValidateRequest(ctx, requestValidationInput)
if testcase.errStr == "" {
require.NoError(t, err)
} else {
require.ErrorContains(t, err, testcase.errStr)
}
},
)
}
}
kin-openapi-0.124.0/openapi3filter/issue639_test.go 0000664 0000000 0000000 00000004451 14604223742 0022004 0 ustar 00root root 0000000 0000000 package openapi3filter
import (
"encoding/json"
"io"
"net/http"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/routers/gorillamux"
)
func TestIssue639(t *testing.T) {
loader := openapi3.NewLoader()
ctx := loader.Context
spec := `
openapi: 3.0.0
info:
version: 1.0.0
title: Sample API
paths:
/items:
put:
requestBody:
content:
application/json:
schema:
properties:
testWithdefault:
default: false
type: boolean
testNoDefault:
type: boolean
type: object
responses:
'200':
description: Successful response
`[1:]
doc, err := loader.LoadFromData([]byte(spec))
require.NoError(t, err)
err = doc.Validate(ctx)
require.NoError(t, err)
router, err := gorillamux.NewRouter(doc)
require.NoError(t, err)
tests := []struct {
name string
options *Options
expectedDefaultVal interface{}
}{
{
name: "no defaults are added to requests",
options: &Options{
SkipSettingDefaults: true,
},
expectedDefaultVal: nil,
},
{
name: "defaults are added to requests",
expectedDefaultVal: false,
},
}
for _, testcase := range tests {
t.Run(testcase.name, func(t *testing.T) {
body := "{\"testNoDefault\": true}"
httpReq, err := http.NewRequest(http.MethodPut, "/items", strings.NewReader(body))
require.NoError(t, err)
httpReq.Header.Set("Content-Type", "application/json")
require.NoError(t, err)
route, pathParams, err := router.FindRoute(httpReq)
require.NoError(t, err)
requestValidationInput := &RequestValidationInput{
Request: httpReq,
PathParams: pathParams,
Route: route,
Options: testcase.options,
}
err = ValidateRequest(ctx, requestValidationInput)
require.NoError(t, err)
bodyAfterValidation, err := io.ReadAll(httpReq.Body)
require.NoError(t, err)
raw := map[string]interface{}{}
err = json.Unmarshal(bodyAfterValidation, &raw)
require.NoError(t, err)
require.Equal(t, testcase.expectedDefaultVal,
raw["testWithdefault"], "default value must not be included")
})
}
}
kin-openapi-0.124.0/openapi3filter/issue641_test.go 0000664 0000000 0000000 00000004513 14604223742 0021774 0 ustar 00root root 0000000 0000000 package openapi3filter_test
import (
"net/http"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/openapi3filter"
"github.com/getkin/kin-openapi/routers/gorillamux"
)
func TestIssue641(t *testing.T) {
anyOfSpec := `
openapi: 3.0.0
info:
version: 1.0.0
title: Sample API
paths:
/items:
get:
description: Returns a list of stuff
parameters:
- description: test object
explode: false
in: query
name: test
required: false
schema:
anyOf:
- pattern: "^[0-9]{1,4}$"
- pattern: "^[0-9]{1,4}$"
type: string
responses:
'200':
description: Successful response
`[1:]
allOfSpec := strings.ReplaceAll(anyOfSpec, "anyOf", "allOf")
tests := []struct {
name string
spec string
req string
errStr string
}{
{
name: "success anyof pattern",
spec: anyOfSpec,
req: "/items?test=51",
},
{
name: "failed anyof pattern",
spec: anyOfSpec,
req: "/items?test=999999",
errStr: `parameter "test" in query has an error: doesn't match any schema from "anyOf"`,
},
{
name: "success allof pattern",
spec: allOfSpec,
req: `/items?test=51`,
},
{
name: "failed allof pattern",
spec: allOfSpec,
req: `/items?test=999999`,
errStr: `parameter "test" in query has an error: string doesn't match the regular expression "^[0-9]{1,4}$"`,
},
}
for _, testcase := range tests {
t.Run(testcase.name, func(t *testing.T) {
loader := openapi3.NewLoader()
ctx := loader.Context
doc, err := loader.LoadFromData([]byte(testcase.spec))
require.NoError(t, err)
err = doc.Validate(ctx)
require.NoError(t, err)
router, err := gorillamux.NewRouter(doc)
require.NoError(t, err)
httpReq, err := http.NewRequest(http.MethodGet, testcase.req, nil)
require.NoError(t, err)
route, pathParams, err := router.FindRoute(httpReq)
require.NoError(t, err)
requestValidationInput := &openapi3filter.RequestValidationInput{
Request: httpReq,
PathParams: pathParams,
Route: route,
}
err = openapi3filter.ValidateRequest(ctx, requestValidationInput)
if testcase.errStr == "" {
require.NoError(t, err)
} else {
require.ErrorContains(t, err, testcase.errStr)
}
},
)
}
}
kin-openapi-0.124.0/openapi3filter/issue689_test.go 0000664 0000000 0000000 00000010557 14604223742 0022015 0 ustar 00root root 0000000 0000000 package openapi3filter
import (
"io"
"net/http"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/routers/gorillamux"
)
func TestIssue689(t *testing.T) {
loader := openapi3.NewLoader()
ctx := loader.Context
spec := `
openapi: 3.0.0
info:
version: 1.0.0
title: Sample API
paths:
/items:
put:
requestBody:
content:
application/json:
schema:
properties:
testWithReadOnly:
readOnly: true
type: boolean
testNoReadOnly:
type: boolean
type: object
responses:
'200':
description: OK
get:
responses:
'200':
description: OK
content:
application/json:
schema:
properties:
testWithWriteOnly:
writeOnly: true
type: boolean
testNoWriteOnly:
type: boolean
`[1:]
doc, err := loader.LoadFromData([]byte(spec))
require.NoError(t, err)
err = doc.Validate(ctx)
require.NoError(t, err)
router, err := gorillamux.NewRouter(doc)
require.NoError(t, err)
tests := []struct {
name string
options *Options
body string
method string
checkErr require.ErrorAssertionFunc
}{
// read-only
{
name: "non read-only property is added to request when validation enabled",
body: `{"testNoReadOnly": true}`,
method: http.MethodPut,
checkErr: require.NoError,
},
{
name: "non read-only property is added to request when validation disabled",
body: `{"testNoReadOnly": true}`,
method: http.MethodPut,
options: &Options{
ExcludeReadOnlyValidations: true,
},
checkErr: require.NoError,
},
{
name: "read-only property is added to requests when validation enabled",
body: `{"testWithReadOnly": true}`,
method: http.MethodPut,
checkErr: require.Error,
},
{
name: "read-only property is added to requests when validation disabled",
body: `{"testWithReadOnly": true}`,
method: http.MethodPut,
options: &Options{
ExcludeReadOnlyValidations: true,
},
checkErr: require.NoError,
},
// write-only
{
name: "non write-only property is added to request when validation enabled",
body: `{"testNoWriteOnly": true}`,
method: http.MethodGet,
checkErr: require.NoError,
},
{
name: "non write-only property is added to request when validation disabled",
body: `{"testNoWriteOnly": true}`,
method: http.MethodGet,
options: &Options{
ExcludeWriteOnlyValidations: true,
},
checkErr: require.NoError,
},
{
name: "write-only property is added to requests when validation enabled",
body: `{"testWithWriteOnly": true}`,
method: http.MethodGet,
checkErr: require.Error,
},
{
name: "write-only property is added to requests when validation disabled",
body: `{"testWithWriteOnly": true}`,
method: http.MethodGet,
options: &Options{
ExcludeWriteOnlyValidations: true,
},
checkErr: require.NoError,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
httpReq, err := http.NewRequest(test.method, "/items", strings.NewReader(test.body))
require.NoError(t, err)
httpReq.Header.Set("Content-Type", "application/json")
require.NoError(t, err)
route, pathParams, err := router.FindRoute(httpReq)
require.NoError(t, err)
requestValidationInput := &RequestValidationInput{
Request: httpReq,
PathParams: pathParams,
Route: route,
Options: test.options,
}
if test.method == http.MethodGet {
responseValidationInput := &ResponseValidationInput{
RequestValidationInput: requestValidationInput,
Status: 200,
Header: httpReq.Header,
Body: io.NopCloser(strings.NewReader(test.body)),
Options: test.options,
}
err = ValidateResponse(ctx, responseValidationInput)
} else {
err = ValidateRequest(ctx, requestValidationInput)
}
test.checkErr(t, err)
})
}
}
kin-openapi-0.124.0/openapi3filter/issue707_test.go 0000664 0000000 0000000 00000003646 14604223742 0022005 0 ustar 00root root 0000000 0000000 package openapi3filter
import (
"net/http"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/routers/gorillamux"
)
func TestIssue707(t *testing.T) {
loader := openapi3.NewLoader()
ctx := loader.Context
spec := `
openapi: 3.0.0
info:
version: 1.0.0
title: Sample API
paths:
/items:
get:
description: Returns a list of stuff
parameters:
- description: parameter with a default value
explode: true
in: query
name: param-with-default
schema:
default: 124
type: integer
required: false
responses:
'200':
description: Successful response
`[1:]
doc, err := loader.LoadFromData([]byte(spec))
require.NoError(t, err)
err = doc.Validate(ctx)
require.NoError(t, err)
router, err := gorillamux.NewRouter(doc)
require.NoError(t, err)
tests := []struct {
name string
options *Options
expectedQuery string
}{
{
name: "no defaults are added to requests parameters",
options: &Options{
SkipSettingDefaults: true,
},
expectedQuery: "",
},
{
name: "defaults are added to requests",
expectedQuery: "param-with-default=124",
},
}
for _, testcase := range tests {
t.Run(testcase.name, func(t *testing.T) {
httpReq, err := http.NewRequest(http.MethodGet, "/items", strings.NewReader(""))
require.NoError(t, err)
route, pathParams, err := router.FindRoute(httpReq)
require.NoError(t, err)
requestValidationInput := &RequestValidationInput{
Request: httpReq,
PathParams: pathParams,
Route: route,
Options: testcase.options,
}
err = ValidateRequest(ctx, requestValidationInput)
require.NoError(t, err)
require.NoError(t, err)
require.Equal(t, testcase.expectedQuery,
httpReq.URL.RawQuery, "default value must not be included")
})
}
}
kin-openapi-0.124.0/openapi3filter/issue722_test.go 0000664 0000000 0000000 00000005354 14604223742 0022000 0 ustar 00root root 0000000 0000000 package openapi3filter_test
import (
"bytes"
"context"
"io"
"mime/multipart"
"net/http"
"net/textproto"
"strings"
"testing"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/openapi3filter"
"github.com/getkin/kin-openapi/routers/gorillamux"
)
func TestValidateMultipartFormDataContainingAllOf(t *testing.T) {
const spec = `
openapi: 3.0.0
info:
title: 'Validator'
version: 0.0.1
paths:
/test:
post:
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required:
- file
allOf:
- $ref: '#/components/schemas/Category'
- properties:
file:
type: string
format: binary
description:
type: string
responses:
'200':
description: Created
components:
schemas:
Category:
type: object
properties:
name:
type: string
required:
- name
`
loader := openapi3.NewLoader()
doc, err := loader.LoadFromData([]byte(spec))
if err != nil {
t.Fatal(err)
}
if err = doc.Validate(loader.Context); err != nil {
t.Fatal(err)
}
router, err := gorillamux.NewRouter(doc)
if err != nil {
t.Fatal(err)
}
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
{ // Add file data
fw, err := writer.CreateFormFile("file", "hello.txt")
if err != nil {
t.Fatal(err)
}
if _, err = io.Copy(fw, strings.NewReader("hello")); err != nil {
t.Fatal(err)
}
}
{ // Add a single "name" item as part data
h := make(textproto.MIMEHeader)
h.Set("Content-Disposition", `form-data; name="name"`)
fw, err := writer.CreatePart(h)
if err != nil {
t.Fatal(err)
}
if _, err = io.Copy(fw, strings.NewReader(`foo`)); err != nil {
t.Fatal(err)
}
}
{ // Add a single "description" item as part data
h := make(textproto.MIMEHeader)
h.Set("Content-Disposition", `form-data; name="description"`)
fw, err := writer.CreatePart(h)
if err != nil {
t.Fatal(err)
}
if _, err = io.Copy(fw, strings.NewReader(`description note`)); err != nil {
t.Fatal(err)
}
}
writer.Close()
req, err := http.NewRequest(http.MethodPost, "/test", bytes.NewReader(body.Bytes()))
if err != nil {
t.Fatal(err)
}
req.Header.Set("Content-Type", writer.FormDataContentType())
route, pathParams, err := router.FindRoute(req)
if err != nil {
t.Fatal(err)
}
if err = openapi3filter.ValidateRequestBody(
context.Background(),
&openapi3filter.RequestValidationInput{
Request: req,
PathParams: pathParams,
Route: route,
},
route.Operation.RequestBody.Value,
); err != nil {
t.Error(err)
}
}
kin-openapi-0.124.0/openapi3filter/issue733_test.go 0000664 0000000 0000000 00000005160 14604223742 0021775 0 ustar 00root root 0000000 0000000 package openapi3filter_test
import (
"bytes"
"context"
"encoding/json"
"math"
"math/big"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/openapi3filter"
"github.com/getkin/kin-openapi/routers/gorillamux"
)
func TestIntMax(t *testing.T) {
spec := `
openapi: 3.0.0
info:
version: 1.0.0
title: test large integer value
paths:
/test:
post:
requestBody:
content:
application/json:
schema:
type: object
properties:
testInteger:
type: integer
format: int64
testDefault:
type: boolean
default: false
responses:
'200':
description: Successful response
`[1:]
loader := openapi3.NewLoader()
doc, err := loader.LoadFromData([]byte(spec))
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
router, err := gorillamux.NewRouter(doc)
require.NoError(t, err)
testOne := func(value *big.Int, pass bool) {
valueString := value.String()
req, err := http.NewRequest(http.MethodPost, "/test", bytes.NewReader([]byte(`{"testInteger":`+valueString+`}`)))
require.NoError(t, err)
req.Header.Set("Content-Type", "application/json")
route, pathParams, err := router.FindRoute(req)
require.NoError(t, err)
err = openapi3filter.ValidateRequest(
context.Background(),
&openapi3filter.RequestValidationInput{
Request: req,
PathParams: pathParams,
Route: route,
})
if pass {
require.NoError(t, err)
dec := json.NewDecoder(req.Body)
dec.UseNumber()
var jsonAfter map[string]interface{}
err = dec.Decode(&jsonAfter)
require.NoError(t, err)
valueAfter := jsonAfter["testInteger"]
require.IsType(t, json.Number(""), valueAfter)
assert.Equal(t, valueString, string(valueAfter.(json.Number)))
} else {
if assert.Error(t, err) {
var serr *openapi3.SchemaError
if assert.ErrorAs(t, err, &serr) {
assert.Equal(t, "number must be an int64", serr.Reason)
}
}
}
}
bigMaxInt64 := big.NewInt(math.MaxInt64)
bigMaxInt64Plus1 := new(big.Int).Add(bigMaxInt64, big.NewInt(1))
bigMinInt64 := big.NewInt(math.MinInt64)
bigMinInt64Minus1 := new(big.Int).Sub(bigMinInt64, big.NewInt(1))
testOne(bigMaxInt64, true)
// XXX not yet fixed
// testOne(bigMaxInt64Plus1, false)
testOne(bigMaxInt64Plus1, true)
testOne(bigMinInt64, true)
// XXX not yet fixed
// testOne(bigMinInt64Minus1, false)
testOne(bigMinInt64Minus1, true)
}
kin-openapi-0.124.0/openapi3filter/issue789_test.go 0000664 0000000 0000000 00000006074 14604223742 0022015 0 ustar 00root root 0000000 0000000 package openapi3filter_test
import (
"net/http"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/openapi3filter"
"github.com/getkin/kin-openapi/routers/gorillamux"
)
func TestIssue789(t *testing.T) {
anyOfArraySpec := `
openapi: 3.0.0
info:
version: 1.0.0
title: Sample API
paths:
/items:
get:
description: Returns a list of stuff
parameters:
- description: test object
explode: false
in: query
name: test
required: true
schema:
type: string
anyOf:
- pattern: '\babc\b'
- pattern: '\bfoo\b'
- pattern: '\bbar\b'
responses:
'200':
description: Successful response
`[1:]
oneOfArraySpec := strings.ReplaceAll(anyOfArraySpec, "anyOf", "oneOf")
allOfArraySpec := strings.ReplaceAll(anyOfArraySpec, "anyOf", "allOf")
tests := []struct {
name string
spec string
req string
errStr string
}{
{
name: "success anyof string pattern match",
spec: anyOfArraySpec,
req: "/items?test=abc",
},
{
name: "failed anyof string pattern match",
spec: anyOfArraySpec,
req: "/items?test=def",
errStr: `parameter "test" in query has an error: doesn't match any schema from "anyOf"`,
},
{
name: "success allof object array",
spec: allOfArraySpec,
req: `/items?test=abc foo bar`,
},
{
name: "failed allof object array",
spec: allOfArraySpec,
req: `/items?test=foo`,
errStr: `parameter "test" in query has an error: string doesn't match the regular expression`,
},
{
name: "success oneof string pattern match",
spec: oneOfArraySpec,
req: `/items?test=foo`,
},
{
name: "failed oneof string pattern match",
spec: oneOfArraySpec,
req: `/items?test=def`,
errStr: `parameter "test" in query has an error: doesn't match schema due to: string doesn't match the regular expression`,
},
{
name: "failed oneof string pattern match",
spec: oneOfArraySpec,
req: `/items?test=foo bar`,
errStr: `parameter "test" in query has an error: input matches more than one oneOf schemas`,
},
}
for _, testcase := range tests {
t.Run(testcase.name, func(t *testing.T) {
loader := openapi3.NewLoader()
ctx := loader.Context
doc, err := loader.LoadFromData([]byte(testcase.spec))
require.NoError(t, err)
err = doc.Validate(ctx)
require.NoError(t, err)
router, err := gorillamux.NewRouter(doc)
require.NoError(t, err)
httpReq, err := http.NewRequest(http.MethodGet, testcase.req, nil)
require.NoError(t, err)
route, pathParams, err := router.FindRoute(httpReq)
require.NoError(t, err)
requestValidationInput := &openapi3filter.RequestValidationInput{
Request: httpReq,
PathParams: pathParams,
Route: route,
}
err = openapi3filter.ValidateRequest(ctx, requestValidationInput)
if testcase.errStr == "" {
require.NoError(t, err)
} else {
require.ErrorContains(t, err, testcase.errStr)
}
},
)
}
}
kin-openapi-0.124.0/openapi3filter/issue884_test.go 0000664 0000000 0000000 00000005117 14604223742 0022006 0 ustar 00root root 0000000 0000000 package openapi3filter
import (
"net/http"
"net/url"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/routers/gorillamux"
)
func TestIssue884(t *testing.T) {
loader := openapi3.NewLoader()
ctx := loader.Context
spec := `
openapi: 3.0.0
info:
version: 1.0.0
title: Sample API
components:
schemas:
TaskSortEnum:
enum:
- createdAt
- -createdAt
- updatedAt
- -updatedAt
paths:
/tasks:
get:
operationId: ListTask
parameters:
- in: query
name: withDefault
schema:
allOf:
- $ref: '#/components/schemas/TaskSortEnum'
- default: -createdAt
- in: query
name: withoutDefault
schema:
allOf:
- $ref: '#/components/schemas/TaskSortEnum'
- in: query
name: withManyDefaults
schema:
allOf:
- default: -updatedAt
- $ref: '#/components/schemas/TaskSortEnum'
- default: -createdAt
responses:
'200':
description: Successful response
`[1:]
doc, err := loader.LoadFromData([]byte(spec))
require.NoError(t, err)
err = doc.Validate(ctx)
require.NoError(t, err)
router, err := gorillamux.NewRouter(doc)
require.NoError(t, err)
tests := []struct {
name string
options *Options
expectedQuery url.Values
}{
{
name: "no defaults are added to requests",
options: &Options{
SkipSettingDefaults: true,
},
expectedQuery: url.Values{},
},
{
name: "defaults are added to requests",
expectedQuery: url.Values{
"withDefault": []string{"-createdAt"},
"withManyDefaults": []string{"-updatedAt"}, // first default is win
},
},
}
for _, testcase := range tests {
t.Run(testcase.name, func(t *testing.T) {
httpReq, err := http.NewRequest(http.MethodGet, "/tasks", nil)
require.NoError(t, err)
httpReq.Header.Set("Content-Type", "application/json")
require.NoError(t, err)
route, pathParams, err := router.FindRoute(httpReq)
require.NoError(t, err)
requestValidationInput := &RequestValidationInput{
Request: httpReq,
PathParams: pathParams,
Route: route,
Options: testcase.options,
}
err = ValidateRequest(ctx, requestValidationInput)
require.NoError(t, err)
q := httpReq.URL.Query()
assert.Equal(t, testcase.expectedQuery, q)
})
}
}
kin-openapi-0.124.0/openapi3filter/middleware.go 0000664 0000000 0000000 00000016520 14604223742 0021470 0 ustar 00root root 0000000 0000000 package openapi3filter
import (
"bytes"
"io"
"log"
"net/http"
"github.com/getkin/kin-openapi/routers"
)
// Validator provides HTTP request and response validation middleware.
type Validator struct {
router routers.Router
errFunc ErrFunc
logFunc LogFunc
strict bool
options Options
}
// ErrFunc handles errors that may occur during validation.
type ErrFunc func(w http.ResponseWriter, status int, code ErrCode, err error)
// LogFunc handles log messages that may occur during validation.
type LogFunc func(message string, err error)
// ErrCode is used for classification of different types of errors that may
// occur during validation. These may be used to write an appropriate response
// in ErrFunc.
type ErrCode int
const (
// ErrCodeOK indicates no error. It is also the default value.
ErrCodeOK = 0
// ErrCodeCannotFindRoute happens when the validator fails to resolve the
// request to a defined OpenAPI route.
ErrCodeCannotFindRoute = iota
// ErrCodeRequestInvalid happens when the inbound request does not conform
// to the OpenAPI 3 specification.
ErrCodeRequestInvalid = iota
// ErrCodeResponseInvalid happens when the wrapped handler response does
// not conform to the OpenAPI 3 specification.
ErrCodeResponseInvalid = iota
)
func (e ErrCode) responseText() string {
switch e {
case ErrCodeOK:
return "OK"
case ErrCodeCannotFindRoute:
return "not found"
case ErrCodeRequestInvalid:
return "bad request"
default:
return "server error"
}
}
// NewValidator returns a new response validation middleware, using the given
// routes from an OpenAPI 3 specification.
func NewValidator(router routers.Router, options ...ValidatorOption) *Validator {
v := &Validator{
router: router,
errFunc: func(w http.ResponseWriter, status int, code ErrCode, _ error) {
http.Error(w, code.responseText(), status)
},
logFunc: func(message string, err error) {
log.Printf("%s: %v", message, err)
},
}
for i := range options {
options[i](v)
}
return v
}
// ValidatorOption defines an option that may be specified when creating a
// Validator.
type ValidatorOption func(*Validator)
// OnErr provides a callback that handles writing an HTTP response on a
// validation error. This allows customization of error responses without
// prescribing a particular form. This callback is only called on response
// validator errors in Strict mode.
func OnErr(f ErrFunc) ValidatorOption {
return func(v *Validator) {
v.errFunc = f
}
}
// OnLog provides a callback that handles logging in the Validator. This allows
// the validator to integrate with a services' existing logging system without
// prescribing a particular one.
func OnLog(f LogFunc) ValidatorOption {
return func(v *Validator) {
v.logFunc = f
}
}
// Strict, if set, causes an internal server error to be sent if the wrapped
// handler response fails response validation. If not set, the response is sent
// and the error is only logged.
func Strict(strict bool) ValidatorOption {
return func(v *Validator) {
v.strict = strict
}
}
// ValidationOptions sets request/response validation options on the validator.
func ValidationOptions(options Options) ValidatorOption {
return func(v *Validator) {
v.options = options
}
}
// Middleware returns an http.Handler which wraps the given handler with
// request and response validation.
func (v *Validator) Middleware(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
route, pathParams, err := v.router.FindRoute(r)
if err != nil {
v.logFunc("validation error: failed to find route for "+r.URL.String(), err)
v.errFunc(w, http.StatusNotFound, ErrCodeCannotFindRoute, err)
return
}
requestValidationInput := &RequestValidationInput{
Request: r,
PathParams: pathParams,
Route: route,
Options: &v.options,
}
if err = ValidateRequest(r.Context(), requestValidationInput); err != nil {
v.logFunc("invalid request", err)
v.errFunc(w, http.StatusBadRequest, ErrCodeRequestInvalid, err)
return
}
var wr responseWrapper
if v.strict {
wr = &strictResponseWrapper{w: w}
} else {
wr = newWarnResponseWrapper(w)
}
h.ServeHTTP(wr, r)
if err = ValidateResponse(r.Context(), &ResponseValidationInput{
RequestValidationInput: requestValidationInput,
Status: wr.statusCode(),
Header: wr.Header(),
Body: io.NopCloser(bytes.NewBuffer(wr.bodyContents())),
Options: &v.options,
}); err != nil {
v.logFunc("invalid response", err)
if v.strict {
v.errFunc(w, http.StatusInternalServerError, ErrCodeResponseInvalid, err)
}
return
}
if err = wr.flushBodyContents(); err != nil {
v.logFunc("failed to write response", err)
}
})
}
type responseWrapper interface {
http.ResponseWriter
// flushBodyContents writes the buffered response to the client, if it has
// not yet been written.
flushBodyContents() error
// statusCode returns the response status code, 0 if not set yet.
statusCode() int
// bodyContents returns the buffered
bodyContents() []byte
}
type warnResponseWrapper struct {
w http.ResponseWriter
headerWritten bool
status int
body bytes.Buffer
tee io.Writer
}
func newWarnResponseWrapper(w http.ResponseWriter) *warnResponseWrapper {
wr := &warnResponseWrapper{
w: w,
}
wr.tee = io.MultiWriter(w, &wr.body)
return wr
}
// Write implements http.ResponseWriter.
func (wr *warnResponseWrapper) Write(b []byte) (int, error) {
if !wr.headerWritten {
wr.WriteHeader(http.StatusOK)
}
return wr.tee.Write(b)
}
// WriteHeader implements http.ResponseWriter.
func (wr *warnResponseWrapper) WriteHeader(status int) {
if !wr.headerWritten {
// If the header hasn't been written, record the status for response
// validation.
wr.status = status
wr.headerWritten = true
}
wr.w.WriteHeader(wr.status)
}
// Header implements http.ResponseWriter.
func (wr *warnResponseWrapper) Header() http.Header {
return wr.w.Header()
}
// Flush implements the optional http.Flusher interface.
func (wr *warnResponseWrapper) Flush() {
// If the wrapped http.ResponseWriter implements optional http.Flusher,
// pass through.
if fl, ok := wr.w.(http.Flusher); ok {
fl.Flush()
}
}
func (wr *warnResponseWrapper) flushBodyContents() error {
return nil
}
func (wr *warnResponseWrapper) statusCode() int {
return wr.status
}
func (wr *warnResponseWrapper) bodyContents() []byte {
return wr.body.Bytes()
}
type strictResponseWrapper struct {
w http.ResponseWriter
headerWritten bool
status int
body bytes.Buffer
}
// Write implements http.ResponseWriter.
func (wr *strictResponseWrapper) Write(b []byte) (int, error) {
if !wr.headerWritten {
wr.WriteHeader(http.StatusOK)
}
return wr.body.Write(b)
}
// WriteHeader implements http.ResponseWriter.
func (wr *strictResponseWrapper) WriteHeader(status int) {
if !wr.headerWritten {
wr.status = status
wr.headerWritten = true
}
}
// Header implements http.ResponseWriter.
func (wr *strictResponseWrapper) Header() http.Header {
return wr.w.Header()
}
func (wr *strictResponseWrapper) flushBodyContents() error {
wr.w.WriteHeader(wr.status)
_, err := wr.w.Write(wr.body.Bytes())
return err
}
func (wr *strictResponseWrapper) statusCode() int {
return wr.status
}
func (wr *strictResponseWrapper) bodyContents() []byte {
return wr.body.Bytes()
}
kin-openapi-0.124.0/openapi3filter/middleware_test.go 0000664 0000000 0000000 00000037444 14604223742 0022537 0 ustar 00root root 0000000 0000000 package openapi3filter_test
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"path"
"regexp"
"strconv"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/openapi3filter"
"github.com/getkin/kin-openapi/routers/gorillamux"
)
const validatorSpec = `
openapi: 3.0.0
info:
title: 'Validator'
version: '0.0.0'
paths:
/test:
post:
operationId: newTest
description: create a new test
parameters:
- in: query
name: version
schema:
type: string
required: true
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/TestContents' }
responses:
'201':
description: 'created test'
content:
application/json:
schema: { $ref: '#/components/schemas/TestResource' }
'400': { $ref: '#/components/responses/ErrorResponse' }
'500': { $ref: '#/components/responses/ErrorResponse' }
/test/{id}:
get:
operationId: getTest
description: get a test
parameters:
- in: path
name: id
schema:
type: string
required: true
- in: query
name: version
schema:
type: string
required: true
responses:
'200':
description: 'respond with test resource'
content:
application/json:
schema: { $ref: '#/components/schemas/TestResource' }
'400': { $ref: '#/components/responses/ErrorResponse' }
'404': { $ref: '#/components/responses/ErrorResponse' }
'500': { $ref: '#/components/responses/ErrorResponse' }
components:
schemas:
TestContents:
type: object
properties:
name:
type: string
expected:
type: number
actual:
type: number
required: [name, expected, actual]
additionalProperties: false
TestResource:
type: object
properties:
id:
type: string
contents:
{ $ref: '#/components/schemas/TestContents' }
required: [id, contents]
additionalProperties: false
Error:
type: object
properties:
code:
type: string
message:
type: string
required: [code, message]
additionalProperties: false
responses:
ErrorResponse:
description: 'an error occurred'
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
`
type validatorTestHandler struct {
contentType string
getBody, postBody string
errBody string
errStatusCode int
}
const validatorOkResponse = `{"id": "42", "contents": {"name": "foo", "expected": 9, "actual": 10}}`
func (h validatorTestHandler) withDefaults() validatorTestHandler {
if h.contentType == "" {
h.contentType = "application/json"
}
if h.getBody == "" {
h.getBody = validatorOkResponse
}
if h.postBody == "" {
h.postBody = validatorOkResponse
}
if h.errBody == "" {
h.errBody = `{"code":"bad","message":"bad things"}`
}
return h
}
var testUrlRE = regexp.MustCompile(`^/test(/\d+)?$`)
func (h *validatorTestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", h.contentType)
if h.errStatusCode != 0 {
w.WriteHeader(h.errStatusCode)
w.Write([]byte(h.errBody))
return
}
if !testUrlRE.MatchString(r.URL.Path) {
w.WriteHeader(http.StatusNotFound)
w.Write([]byte(h.errBody))
return
}
switch r.Method {
case "GET":
w.WriteHeader(http.StatusOK)
w.Write([]byte(h.getBody))
case "POST":
w.WriteHeader(http.StatusCreated)
w.Write([]byte(h.postBody))
default:
http.Error(w, h.errBody, http.StatusMethodNotAllowed)
}
}
func TestValidator(t *testing.T) {
loader := openapi3.NewLoader()
doc, err := loader.LoadFromData([]byte(validatorSpec))
require.NoError(t, err, "failed to load test fixture spec")
err = doc.Validate(loader.Context)
require.NoError(t, err, "invalid test fixture spec")
type testRequest struct {
method, path, body, contentType string
}
type testResponse struct {
statusCode int
body string
}
tests := []struct {
name string
handler validatorTestHandler
options []openapi3filter.ValidatorOption
request testRequest
response testResponse
strict bool
}{{
name: "valid GET",
handler: validatorTestHandler{}.withDefaults(),
request: testRequest{
method: "GET",
path: "/test/42?version=1",
},
response: testResponse{
200, validatorOkResponse,
},
strict: true,
}, {
name: "valid POST",
handler: validatorTestHandler{}.withDefaults(),
request: testRequest{
method: "POST",
path: "/test?version=1",
body: `{"name": "foo", "expected": 9, "actual": 10}`,
contentType: "application/json",
},
response: testResponse{
201, validatorOkResponse,
},
strict: true,
}, {
name: "not found; no GET operation for /test",
handler: validatorTestHandler{}.withDefaults(),
request: testRequest{
method: "GET",
path: "/test?version=1",
},
response: testResponse{
404, "not found\n",
},
strict: true,
}, {
name: "not found; no POST operation for /test/42",
handler: validatorTestHandler{}.withDefaults(),
request: testRequest{
method: "POST",
path: "/test/42?version=1",
},
response: testResponse{
404, "not found\n",
},
strict: true,
}, {
name: "invalid request; missing version",
handler: validatorTestHandler{}.withDefaults(),
request: testRequest{
method: "GET",
path: "/test/42",
},
response: testResponse{
400, "bad request\n",
},
strict: true,
}, {
name: "invalid POST request; wrong property type",
handler: validatorTestHandler{}.withDefaults(),
request: testRequest{
method: "POST",
path: "/test?version=1",
body: `{"name": "foo", "expected": "nine", "actual": "ten"}`,
contentType: "application/json",
},
response: testResponse{
400, "bad request\n",
},
strict: true,
}, {
name: "invalid POST request; missing property",
handler: validatorTestHandler{}.withDefaults(),
request: testRequest{
method: "POST",
path: "/test?version=1",
body: `{"name": "foo", "expected": 9}`,
contentType: "application/json",
},
response: testResponse{
400, "bad request\n",
},
strict: true,
}, {
name: "invalid POST request; extra property",
handler: validatorTestHandler{}.withDefaults(),
request: testRequest{
method: "POST",
path: "/test?version=1",
body: `{"name": "foo", "expected": 9, "actual": 10, "ideal": 8}`,
contentType: "application/json",
},
response: testResponse{
400, "bad request\n",
},
strict: true,
}, {
name: "valid response; 404 error",
handler: validatorTestHandler{
contentType: "application/json",
errBody: `{"code": "404", "message": "not found"}`,
errStatusCode: 404,
}.withDefaults(),
request: testRequest{
method: "GET",
path: "/test/42?version=1",
},
response: testResponse{
404, `{"code": "404", "message": "not found"}`,
},
strict: true,
}, {
name: "invalid response; invalid error",
handler: validatorTestHandler{
errBody: `"not found"`,
errStatusCode: 404,
}.withDefaults(),
request: testRequest{
method: "GET",
path: "/test/42?version=1",
},
response: testResponse{
500, "server error\n",
},
strict: true,
}, {
name: "invalid POST response; not strict",
handler: validatorTestHandler{
postBody: `{"id": "42", "contents": {"name": "foo", "expected": 9, "actual": 10}, "extra": true}`,
}.withDefaults(),
request: testRequest{
method: "POST",
path: "/test?version=1",
body: `{"name": "foo", "expected": 9, "actual": 10}`,
contentType: "application/json",
},
response: testResponse{
statusCode: 201,
body: `{"id": "42", "contents": {"name": "foo", "expected": 9, "actual": 10}, "extra": true}`,
},
strict: false,
}, {
name: "POST response status code not in spec (return 200, spec only has 201)",
handler: validatorTestHandler{
postBody: `{"id": "42", "contents": {"name": "foo", "expected": 9, "actual": 10}, "extra": true}`,
errStatusCode: 200,
errBody: `{"id": "42", "contents": {"name": "foo", "expected": 9, "actual": 10}, "extra": true}`,
}.withDefaults(),
options: []openapi3filter.ValidatorOption{openapi3filter.ValidationOptions(openapi3filter.Options{
IncludeResponseStatus: true,
})},
request: testRequest{
method: "POST",
path: "/test?version=1",
body: `{"name": "foo", "expected": 9, "actual": 10}`,
contentType: "application/json",
},
response: testResponse{
statusCode: 200,
body: `{"id": "42", "contents": {"name": "foo", "expected": 9, "actual": 10}, "extra": true}`,
},
strict: false,
}}
for i, test := range tests {
t.Logf("test#%d: %s", i, test.name)
t.Run(test.name, func(t *testing.T) {
// Set up a test HTTP server
var h http.Handler
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
h.ServeHTTP(w, r)
}))
defer s.Close()
// Update the OpenAPI servers section with the test server URL This is
// needed by the router which matches request routes for OpenAPI
// validation.
doc.Servers = []*openapi3.Server{{URL: s.URL}}
err = doc.Validate(loader.Context)
require.NoError(t, err, "failed to validate with test server")
// Create the router and validator
router, err := gorillamux.NewRouter(doc)
require.NoError(t, err, "failed to create router")
// Now wrap the test handler with the validator middleware
v := openapi3filter.NewValidator(router, append(test.options, openapi3filter.Strict(test.strict))...)
h = v.Middleware(&test.handler)
// Test: make a client request
var requestBody io.Reader
if test.request.body != "" {
requestBody = bytes.NewBufferString(test.request.body)
}
req, err := http.NewRequest(test.request.method, s.URL+test.request.path, requestBody)
require.NoError(t, err, "failed to create request")
if test.request.contentType != "" {
req.Header.Set("Content-Type", test.request.contentType)
}
resp, err := s.Client().Do(req)
require.NoError(t, err, "request failed")
defer resp.Body.Close()
require.Equalf(t, test.response.statusCode, resp.StatusCode,
"response code expect %d got %d", test.response.statusCode, resp.StatusCode)
body, err := io.ReadAll(resp.Body)
require.NoError(t, err, "failed to read response body")
require.Equalf(t, test.response.body, string(body),
"response body expect %q got %q", test.response.body, string(body))
})
}
}
func ExampleValidator() {
// OpenAPI specification for a simple service that squares integers, with
// some limitations.
loader := openapi3.NewLoader()
doc, err := loader.LoadFromData([]byte(`
openapi: 3.0.0
info:
title: 'Validator - square example'
version: '0.0.0'
paths:
/square/{x}:
get:
description: square an integer
parameters:
- name: x
in: path
schema:
type: integer
required: true
responses:
'200':
description: squared integer response
content:
"application/json":
schema:
type: object
properties:
result:
type: integer
minimum: 0
maximum: 1000000
required: [result]
additionalProperties: false
`[1:]))
if err != nil {
panic(err)
}
// Make sure that OpenAPI document is correct
if err = doc.Validate(loader.Context); err != nil {
panic(err)
}
// Square service handler sanity checks inputs, but just crashes on invalid
// requests.
squareHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
xParam := path.Base(r.URL.Path)
x, err := strconv.ParseInt(xParam, 10, 64)
if err != nil {
panic(err)
}
w.Header().Set("Content-Type", "application/json")
result := map[string]interface{}{"result": x * x}
if x == 42 {
// An easter egg. Unfortunately, the spec does not allow additional properties...
result["comment"] = "the answer to the ultimate question of life, the universe, and everything"
}
if err = json.NewEncoder(w).Encode(&result); err != nil {
panic(err)
}
})
// Start an http server.
var mainHandler http.Handler
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Why are we wrapping the main server handler with a closure here?
// Validation matches request Host: to server URLs in the spec. With an
// httptest.Server, the URL is dynamic and we have to create it first!
// In a real configured service, this is less likely to be needed.
mainHandler.ServeHTTP(w, r)
}))
defer srv.Close()
// Patch the OpenAPI spec to match the httptest.Server.URL. Only needed
// because the server URL is dynamic here.
doc.Servers = []*openapi3.Server{{URL: srv.URL}}
if err := doc.Validate(loader.Context); err != nil { // Assert our OpenAPI is valid!
panic(err)
}
// This router is used by the validator to match requests with the OpenAPI
// spec. It does not place restrictions on how the wrapped handler routes
// requests; use of gorilla/mux is just a validator implementation detail.
router, err := gorillamux.NewRouter(doc)
if err != nil {
panic(err)
}
// Strict validation will respond HTTP 500 if the service tries to emit a
// response that does not conform to the OpenAPI spec. Very useful for
// testing a service against its spec in development and CI. In production,
// availability may be more important than strictness.
v := openapi3filter.NewValidator(router, openapi3filter.Strict(true),
openapi3filter.OnErr(func(w http.ResponseWriter, status int, code openapi3filter.ErrCode, err error) {
// Customize validation error responses to use JSON
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(map[string]interface{}{
"status": status,
"message": http.StatusText(status),
})
}))
// Now we can finally set the main server handler.
mainHandler = v.Middleware(squareHandler)
printResp := func(resp *http.Response, err error) {
if err != nil {
panic(err)
}
defer resp.Body.Close()
contents, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Println(resp.StatusCode, strings.TrimSpace(string(contents)))
}
// Valid requests to our sum service
printResp(srv.Client().Get(srv.URL + "/square/2"))
printResp(srv.Client().Get(srv.URL + "/square/789"))
// 404 Not found requests - method or path not found
printResp(srv.Client().Post(srv.URL+"/square/2", "application/json", bytes.NewBufferString(`{"result": 5}`)))
printResp(srv.Client().Get(srv.URL + "/sum/2"))
printResp(srv.Client().Get(srv.URL + "/square/circle/4")) // Handler would process this; validation rejects it
printResp(srv.Client().Get(srv.URL + "/square"))
// 400 Bad requests - note they never reach the wrapped square handler (which would panic)
printResp(srv.Client().Get(srv.URL + "/square/five"))
// 500 Invalid responses
printResp(srv.Client().Get(srv.URL + "/square/42")) // Our "easter egg" added a property which is not allowed
printResp(srv.Client().Get(srv.URL + "/square/65536")) // Answer overflows the maximum allowed value (1000000)
// Output:
// 200 {"result":4}
// 200 {"result":622521}
// 404 {"message":"Not Found","status":404}
// 404 {"message":"Not Found","status":404}
// 404 {"message":"Not Found","status":404}
// 404 {"message":"Not Found","status":404}
// 400 {"message":"Bad Request","status":400}
// 500 {"message":"Internal Server Error","status":500}
// 500 {"message":"Internal Server Error","status":500}
}
kin-openapi-0.124.0/openapi3filter/options.go 0000664 0000000 0000000 00000003165 14604223742 0021047 0 ustar 00root root 0000000 0000000 package openapi3filter
import "github.com/getkin/kin-openapi/openapi3"
// Options used by ValidateRequest and ValidateResponse
type Options struct {
// Set ExcludeRequestBody so ValidateRequest skips request body validation
ExcludeRequestBody bool
// Set ExcludeRequestQueryParams so ValidateRequest skips request query params validation
ExcludeRequestQueryParams bool
// Set ExcludeResponseBody so ValidateResponse skips response body validation
ExcludeResponseBody bool
// Set ExcludeReadOnlyValidations so ValidateRequest skips read-only validations
ExcludeReadOnlyValidations bool
// Set ExcludeWriteOnlyValidations so ValidateResponse skips write-only validations
ExcludeWriteOnlyValidations bool
// Set IncludeResponseStatus so ValidateResponse fails on response
// status not defined in OpenAPI spec
IncludeResponseStatus bool
MultiError bool
// A document with security schemes defined will not pass validation
// unless an AuthenticationFunc is defined.
// See NoopAuthenticationFunc
AuthenticationFunc AuthenticationFunc
// Indicates whether default values are set in the
// request. If true, then they are not set
SkipSettingDefaults bool
customSchemaErrorFunc CustomSchemaErrorFunc
}
// CustomSchemaErrorFunc allows for custom the schema error message.
type CustomSchemaErrorFunc func(err *openapi3.SchemaError) string
// WithCustomSchemaErrorFunc sets a function to override the schema error message.
// If the passed function returns an empty string, it returns to the previous Error() implementation.
func (o *Options) WithCustomSchemaErrorFunc(f CustomSchemaErrorFunc) {
o.customSchemaErrorFunc = f
}
kin-openapi-0.124.0/openapi3filter/options_test.go 0000664 0000000 0000000 00000003344 14604223742 0022105 0 ustar 00root root 0000000 0000000 package openapi3filter_test
import (
"context"
"fmt"
"net/http"
"strings"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/openapi3filter"
"github.com/getkin/kin-openapi/routers/gorillamux"
)
func ExampleOptions_WithCustomSchemaErrorFunc() {
const spec = `
openapi: 3.0.0
info:
title: 'Validator'
version: 0.0.1
paths:
/some:
post:
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
field:
title: Some field
type: integer
responses:
'200':
description: Created
`
loader := openapi3.NewLoader()
doc, err := loader.LoadFromData([]byte(spec))
if err != nil {
panic(err)
}
if err = doc.Validate(loader.Context); err != nil {
panic(err)
}
router, err := gorillamux.NewRouter(doc)
if err != nil {
panic(err)
}
opts := &openapi3filter.Options{}
opts.WithCustomSchemaErrorFunc(func(err *openapi3.SchemaError) string {
return fmt.Sprintf(`field "%s" must be an integer`, err.Schema.Title)
})
req, err := http.NewRequest(http.MethodPost, "/some", strings.NewReader(`{"field":"not integer"}`))
if err != nil {
panic(err)
}
req.Header.Add("Content-Type", "application/json")
route, pathParams, err := router.FindRoute(req)
if err != nil {
panic(err)
}
validationInput := &openapi3filter.RequestValidationInput{
Request: req,
PathParams: pathParams,
Route: route,
Options: opts,
}
err = openapi3filter.ValidateRequest(context.Background(), validationInput)
fmt.Println(err.Error())
// Output: request body has an error: doesn't match schema: field "Some field" must be an integer
}
kin-openapi-0.124.0/openapi3filter/req_resp_decoder.go 0000664 0000000 0000000 00000132440 14604223742 0022660 0 ustar 00root root 0000000 0000000 package openapi3filter
import (
"archive/zip"
"bytes"
"encoding/csv"
"encoding/json"
"errors"
"fmt"
"io"
"mime"
"mime/multipart"
"net/http"
"net/url"
"reflect"
"regexp"
"strconv"
"strings"
"gopkg.in/yaml.v3"
"github.com/getkin/kin-openapi/openapi3"
)
// ParseErrorKind describes a kind of ParseError.
// The type simplifies comparison of errors.
type ParseErrorKind int
const (
// KindOther describes an untyped parsing error.
KindOther ParseErrorKind = iota
// KindUnsupportedFormat describes an error that happens when a value has an unsupported format.
KindUnsupportedFormat
// KindInvalidFormat describes an error that happens when a value does not conform a format
// that is required by a serialization method.
KindInvalidFormat
)
// ParseError describes errors which happens while parse operation's parameters, requestBody, or response.
type ParseError struct {
Kind ParseErrorKind
Value interface{}
Reason string
Cause error
path []interface{}
}
var _ interface{ Unwrap() error } = ParseError{}
func (e *ParseError) Error() string {
var msg []string
if p := e.Path(); len(p) > 0 {
var arr []string
for _, v := range p {
arr = append(arr, fmt.Sprintf("%v", v))
}
msg = append(msg, fmt.Sprintf("path %v", strings.Join(arr, ".")))
}
msg = append(msg, e.innerError())
return strings.Join(msg, ": ")
}
func (e *ParseError) innerError() string {
var msg []string
if e.Value != nil {
msg = append(msg, fmt.Sprintf("value %v", e.Value))
}
if e.Reason != "" {
msg = append(msg, e.Reason)
}
if e.Cause != nil {
if v, ok := e.Cause.(*ParseError); ok {
msg = append(msg, v.innerError())
} else {
msg = append(msg, e.Cause.Error())
}
}
return strings.Join(msg, ": ")
}
// RootCause returns a root cause of ParseError.
func (e *ParseError) RootCause() error {
if v, ok := e.Cause.(*ParseError); ok {
return v.RootCause()
}
return e.Cause
}
func (e ParseError) Unwrap() error {
return e.Cause
}
// Path returns a path to the root cause.
func (e *ParseError) Path() []interface{} {
var path []interface{}
if v, ok := e.Cause.(*ParseError); ok {
p := v.Path()
if len(p) > 0 {
path = append(path, p...)
}
}
if len(e.path) > 0 {
path = append(path, e.path...)
}
return path
}
func invalidSerializationMethodErr(sm *openapi3.SerializationMethod) error {
return fmt.Errorf("invalid serialization method: style=%q, explode=%v", sm.Style, sm.Explode)
}
// Decodes a parameter defined via the content property as an object. It uses
// the user specified decoder, or our build-in decoder for application/json
func decodeContentParameter(param *openapi3.Parameter, input *RequestValidationInput) (
value interface{},
schema *openapi3.Schema,
found bool,
err error,
) {
var paramValues []string
switch param.In {
case openapi3.ParameterInPath:
var paramValue string
if paramValue, found = input.PathParams[param.Name]; found {
paramValues = []string{paramValue}
}
case openapi3.ParameterInQuery:
paramValues, found = input.GetQueryParams()[param.Name]
case openapi3.ParameterInHeader:
var headerValues []string
if headerValues, found = input.Request.Header[http.CanonicalHeaderKey(param.Name)]; found {
paramValues = headerValues
}
case openapi3.ParameterInCookie:
var cookie *http.Cookie
if cookie, err = input.Request.Cookie(param.Name); err == http.ErrNoCookie {
found = false
} else if err != nil {
return
} else {
paramValues = []string{cookie.Value}
found = true
}
default:
err = fmt.Errorf("unsupported parameter.in: %q", param.In)
return
}
if !found {
if param.Required {
err = fmt.Errorf("parameter %q is required, but missing", param.Name)
}
return
}
decoder := input.ParamDecoder
if decoder == nil {
decoder = defaultContentParameterDecoder
}
value, schema, err = decoder(param, paramValues)
return
}
func defaultContentParameterDecoder(param *openapi3.Parameter, values []string) (
outValue interface{},
outSchema *openapi3.Schema,
err error,
) {
// Only query parameters can have multiple values.
if len(values) > 1 && param.In != openapi3.ParameterInQuery {
err = fmt.Errorf("%s parameter %q cannot have multiple values", param.In, param.Name)
return
}
content := param.Content
if content == nil {
err = fmt.Errorf("parameter %q expected to have content", param.Name)
return
}
// We only know how to decode a parameter if it has one content, application/json
if len(content) != 1 {
err = fmt.Errorf("multiple content types for parameter %q", param.Name)
return
}
mt := content.Get("application/json")
if mt == nil {
err = fmt.Errorf("parameter %q has no content schema", param.Name)
return
}
outSchema = mt.Schema.Value
unmarshal := func(encoded string, paramSchema *openapi3.SchemaRef) (decoded interface{}, err error) {
if err = json.Unmarshal([]byte(encoded), &decoded); err != nil {
if paramSchema != nil && !paramSchema.Value.Type.Is("object") {
decoded, err = encoded, nil
}
}
return
}
if len(values) == 1 {
if outValue, err = unmarshal(values[0], mt.Schema); err != nil {
err = fmt.Errorf("error unmarshaling parameter %q", param.Name)
return
}
} else {
outArray := make([]interface{}, 0, len(values))
for _, v := range values {
var item interface{}
if item, err = unmarshal(v, outSchema.Items); err != nil {
err = fmt.Errorf("error unmarshaling parameter %q", param.Name)
return
}
outArray = append(outArray, item)
}
outValue = outArray
}
return
}
type valueDecoder interface {
DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error)
DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]interface{}, bool, error)
DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, bool, error)
}
// decodeStyledParameter returns a value of an operation's parameter from HTTP request for
// parameters defined using the style format, and whether the parameter is supplied in the input.
// The function returns ParseError when HTTP request contains an invalid value of a parameter.
func decodeStyledParameter(param *openapi3.Parameter, input *RequestValidationInput) (interface{}, bool, error) {
sm, err := param.SerializationMethod()
if err != nil {
return nil, false, err
}
var dec valueDecoder
switch param.In {
case openapi3.ParameterInPath:
if len(input.PathParams) == 0 {
return nil, false, nil
}
dec = &pathParamDecoder{pathParams: input.PathParams}
case openapi3.ParameterInQuery:
if len(input.GetQueryParams()) == 0 {
return nil, false, nil
}
dec = &urlValuesDecoder{values: input.GetQueryParams()}
case openapi3.ParameterInHeader:
dec = &headerParamDecoder{header: input.Request.Header}
case openapi3.ParameterInCookie:
dec = &cookieParamDecoder{req: input.Request}
default:
return nil, false, fmt.Errorf("unsupported parameter's 'in': %s", param.In)
}
return decodeValue(dec, param.Name, sm, param.Schema, param.Required)
}
func decodeValue(dec valueDecoder, param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef, required bool) (interface{}, bool, error) {
var found bool
if len(schema.Value.AllOf) > 0 {
var value interface{}
var err error
for _, sr := range schema.Value.AllOf {
var f bool
value, f, err = decodeValue(dec, param, sm, sr, required)
found = found || f
if value == nil || err != nil {
break
}
}
return value, found, err
}
if len(schema.Value.AnyOf) > 0 {
for _, sr := range schema.Value.AnyOf {
value, f, _ := decodeValue(dec, param, sm, sr, required)
found = found || f
if value != nil {
return value, found, nil
}
}
if required {
return nil, found, fmt.Errorf("decoding anyOf for parameter %q failed", param)
}
return nil, found, nil
}
if len(schema.Value.OneOf) > 0 {
isMatched := 0
var value interface{}
for _, sr := range schema.Value.OneOf {
v, f, _ := decodeValue(dec, param, sm, sr, required)
found = found || f
if v != nil {
value = v
isMatched++
}
}
if isMatched >= 1 {
return value, found, nil
}
if required {
return nil, found, fmt.Errorf("decoding oneOf failed: %q is required", param)
}
return nil, found, nil
}
if schema.Value.Not != nil {
// TODO(decode not): handle decoding "not" JSON Schema
return nil, found, errors.New("not implemented: decoding 'not'")
}
if schema.Value.Type != nil {
var decodeFn func(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error)
switch {
case schema.Value.Type.Is("array"):
decodeFn = func(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error) {
return dec.DecodeArray(param, sm, schema)
}
case schema.Value.Type.Is("object"):
decodeFn = func(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error) {
return dec.DecodeObject(param, sm, schema)
}
default:
decodeFn = dec.DecodePrimitive
}
return decodeFn(param, sm, schema)
}
switch vDecoder := dec.(type) {
case *pathParamDecoder:
_, found = vDecoder.pathParams[param]
case *urlValuesDecoder:
if schema.Value.Pattern != "" {
return dec.DecodePrimitive(param, sm, schema)
}
_, found = vDecoder.values[param]
case *headerParamDecoder:
_, found = vDecoder.header[http.CanonicalHeaderKey(param)]
case *cookieParamDecoder:
_, err := vDecoder.req.Cookie(param)
found = err != http.ErrNoCookie
default:
return nil, found, errors.New("unsupported decoder")
}
return nil, found, nil
}
// pathParamDecoder decodes values of path parameters.
type pathParamDecoder struct {
pathParams map[string]string
}
func (d *pathParamDecoder) DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error) {
var prefix string
switch sm.Style {
case "simple":
// A prefix is empty for style "simple".
case "label":
prefix = "."
case "matrix":
prefix = ";" + param + "="
default:
return nil, false, invalidSerializationMethodErr(sm)
}
if d.pathParams == nil {
// HTTP request does not contains a value of the target path parameter.
return nil, false, nil
}
raw, ok := d.pathParams[param]
if !ok || raw == "" {
// HTTP request does not contains a value of the target path parameter.
return nil, false, nil
}
src, err := cutPrefix(raw, prefix)
if err != nil {
return nil, ok, err
}
val, err := parsePrimitive(src, schema)
return val, ok, err
}
func (d *pathParamDecoder) DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]interface{}, bool, error) {
var prefix, delim string
switch {
case sm.Style == "simple":
delim = ","
case sm.Style == "label" && !sm.Explode:
prefix = "."
delim = ","
case sm.Style == "label" && sm.Explode:
prefix = "."
delim = "."
case sm.Style == "matrix" && !sm.Explode:
prefix = ";" + param + "="
delim = ","
case sm.Style == "matrix" && sm.Explode:
prefix = ";" + param + "="
delim = ";" + param + "="
default:
return nil, false, invalidSerializationMethodErr(sm)
}
if d.pathParams == nil {
// HTTP request does not contains a value of the target path parameter.
return nil, false, nil
}
raw, ok := d.pathParams[param]
if !ok || raw == "" {
// HTTP request does not contains a value of the target path parameter.
return nil, false, nil
}
src, err := cutPrefix(raw, prefix)
if err != nil {
return nil, ok, err
}
val, err := parseArray(strings.Split(src, delim), schema)
return val, ok, err
}
func (d *pathParamDecoder) DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, bool, error) {
var prefix, propsDelim, valueDelim string
switch {
case sm.Style == "simple" && !sm.Explode:
propsDelim = ","
valueDelim = ","
case sm.Style == "simple" && sm.Explode:
propsDelim = ","
valueDelim = "="
case sm.Style == "label" && !sm.Explode:
prefix = "."
propsDelim = ","
valueDelim = ","
case sm.Style == "label" && sm.Explode:
prefix = "."
propsDelim = "."
valueDelim = "="
case sm.Style == "matrix" && !sm.Explode:
prefix = ";" + param + "="
propsDelim = ","
valueDelim = ","
case sm.Style == "matrix" && sm.Explode:
prefix = ";"
propsDelim = ";"
valueDelim = "="
default:
return nil, false, invalidSerializationMethodErr(sm)
}
if d.pathParams == nil {
// HTTP request does not contains a value of the target path parameter.
return nil, false, nil
}
raw, ok := d.pathParams[param]
if !ok || raw == "" {
// HTTP request does not contains a value of the target path parameter.
return nil, false, nil
}
src, err := cutPrefix(raw, prefix)
if err != nil {
return nil, ok, err
}
props, err := propsFromString(src, propsDelim, valueDelim)
if err != nil {
return nil, ok, err
}
val, err := makeObject(props, schema)
return val, ok, err
}
// cutPrefix validates that a raw value of a path parameter has the specified prefix,
// and returns a raw value without the prefix.
func cutPrefix(raw, prefix string) (string, error) {
if prefix == "" {
return raw, nil
}
if len(raw) < len(prefix) || raw[:len(prefix)] != prefix {
return "", &ParseError{
Kind: KindInvalidFormat,
Value: raw,
Reason: fmt.Sprintf("a value must be prefixed with %q", prefix),
}
}
return raw[len(prefix):], nil
}
// urlValuesDecoder decodes values of query parameters.
type urlValuesDecoder struct {
values url.Values
}
func (d *urlValuesDecoder) DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error) {
if sm.Style != "form" {
return nil, false, invalidSerializationMethodErr(sm)
}
values, ok := d.values[param]
if len(values) == 0 {
// HTTP request does not contain a value of the target query parameter.
return nil, ok, nil
}
if schema.Value.Type == nil && schema.Value.Pattern != "" {
return values[0], ok, nil
}
val, err := parsePrimitive(values[0], schema)
return val, ok, err
}
func (d *urlValuesDecoder) DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]interface{}, bool, error) {
if sm.Style == "deepObject" {
return nil, false, invalidSerializationMethodErr(sm)
}
values, ok := d.values[param]
if len(values) == 0 {
// HTTP request does not contain a value of the target query parameter.
return nil, ok, nil
}
if !sm.Explode {
var delim string
switch sm.Style {
case "form":
delim = ","
case "spaceDelimited":
delim = " "
case "pipeDelimited":
delim = "|"
}
values = strings.Split(values[0], delim)
}
val, err := d.parseArray(values, sm, schema)
return val, ok, err
}
// parseArray returns an array that contains items from a raw array.
// Every item is parsed as a primitive value.
// The function returns an error when an error happened while parse array's items.
func (d *urlValuesDecoder) parseArray(raw []string, sm *openapi3.SerializationMethod, schemaRef *openapi3.SchemaRef) ([]interface{}, error) {
var value []interface{}
for i, v := range raw {
item, err := d.parseValue(v, schemaRef.Value.Items)
if err != nil {
if v, ok := err.(*ParseError); ok {
return nil, &ParseError{path: []interface{}{i}, Cause: v}
}
return nil, fmt.Errorf("item %d: %w", i, err)
}
// If the items are nil, then the array is nil. There shouldn't be case where some values are actual primitive
// values and some are nil values.
if item == nil {
return nil, nil
}
value = append(value, item)
}
return value, nil
}
func (d *urlValuesDecoder) parseValue(v string, schema *openapi3.SchemaRef) (interface{}, error) {
if len(schema.Value.AllOf) > 0 {
var value interface{}
var err error
for _, sr := range schema.Value.AllOf {
value, err = d.parseValue(v, sr)
if value == nil || err != nil {
break
}
}
return value, err
}
if len(schema.Value.AnyOf) > 0 {
var value interface{}
var err error
for _, sr := range schema.Value.AnyOf {
if value, err = d.parseValue(v, sr); err == nil {
return value, nil
}
}
return nil, err
}
if len(schema.Value.OneOf) > 0 {
isMatched := 0
var value interface{}
var err error
for _, sr := range schema.Value.OneOf {
result, err := d.parseValue(v, sr)
if err == nil {
value = result
isMatched++
}
}
if isMatched == 1 {
return value, nil
} else if isMatched > 1 {
return nil, fmt.Errorf("decoding oneOf failed: %d schemas matched", isMatched)
} else if isMatched == 0 {
return nil, fmt.Errorf("decoding oneOf failed: %d schemas matched", isMatched)
}
return nil, err
}
if schema.Value.Not != nil {
// TODO(decode not): handle decoding "not" JSON Schema
return nil, errors.New("not implemented: decoding 'not'")
}
return parsePrimitive(v, schema)
}
const (
urlDecoderDelimiter = "\x1F" // should not conflict with URL characters
)
func (d *urlValuesDecoder) DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, bool, error) {
var propsFn func(url.Values) (map[string]string, error)
switch sm.Style {
case "form":
propsFn = func(params url.Values) (map[string]string, error) {
if len(params) == 0 {
// HTTP request does not contain query parameters.
return nil, nil
}
if sm.Explode {
props := make(map[string]string)
for key, values := range params {
props[key] = values[0]
}
return props, nil
}
values := params[param]
if len(values) == 0 {
// HTTP request does not contain a value of the target query parameter.
return nil, nil
}
return propsFromString(values[0], ",", ",")
}
case "deepObject":
propsFn = func(params url.Values) (map[string]string, error) {
props := make(map[string]string)
for key, values := range params {
matches := regexp.MustCompile(`\[(.*?)\]`).FindAllStringSubmatch(key, -1)
switch l := len(matches); {
case l == 0:
// A query parameter's name does not match the required format, so skip it.
continue
case l >= 1:
kk := []string{}
for _, m := range matches {
kk = append(kk, m[1])
}
props[strings.Join(kk, urlDecoderDelimiter)] = strings.Join(values, urlDecoderDelimiter)
}
}
if len(props) == 0 {
// HTTP request does not contain query parameters encoded by rules of style "deepObject".
return nil, nil
}
return props, nil
}
default:
return nil, false, invalidSerializationMethodErr(sm)
}
props, err := propsFn(d.values)
if err != nil {
return nil, false, err
}
if props == nil {
return nil, false, nil
}
val, err := makeObject(props, schema)
if err != nil {
return nil, false, err
}
found := false
for propName := range schema.Value.Properties {
if _, ok := props[propName]; ok {
found = true
break
}
if schema.Value.Type.Permits("array") || schema.Value.Type.Permits("object") {
for k := range props {
path := strings.Split(k, urlDecoderDelimiter)
if _, ok := deepGet(val, path...); ok {
found = true
break
}
}
}
}
return val, found, nil
}
// headerParamDecoder decodes values of header parameters.
type headerParamDecoder struct {
header http.Header
}
func (d *headerParamDecoder) DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error) {
if sm.Style != "simple" {
return nil, false, invalidSerializationMethodErr(sm)
}
raw, ok := d.header[http.CanonicalHeaderKey(param)]
if !ok || len(raw) == 0 {
// HTTP request does not contains a corresponding header or has the empty value
return nil, ok, nil
}
val, err := parsePrimitive(raw[0], schema)
return val, ok, err
}
func (d *headerParamDecoder) DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]interface{}, bool, error) {
if sm.Style != "simple" {
return nil, false, invalidSerializationMethodErr(sm)
}
raw, ok := d.header[http.CanonicalHeaderKey(param)]
if !ok || len(raw) == 0 {
// HTTP request does not contains a corresponding header
return nil, ok, nil
}
val, err := parseArray(strings.Split(raw[0], ","), schema)
return val, ok, err
}
func (d *headerParamDecoder) DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, bool, error) {
if sm.Style != "simple" {
return nil, false, invalidSerializationMethodErr(sm)
}
valueDelim := ","
if sm.Explode {
valueDelim = "="
}
raw, ok := d.header[http.CanonicalHeaderKey(param)]
if !ok || len(raw) == 0 {
// HTTP request does not contain a corresponding header.
return nil, ok, nil
}
props, err := propsFromString(raw[0], ",", valueDelim)
if err != nil {
return nil, ok, err
}
val, err := makeObject(props, schema)
return val, ok, err
}
// cookieParamDecoder decodes values of cookie parameters.
type cookieParamDecoder struct {
req *http.Request
}
func (d *cookieParamDecoder) DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error) {
if sm.Style != "form" {
return nil, false, invalidSerializationMethodErr(sm)
}
cookie, err := d.req.Cookie(param)
found := err != http.ErrNoCookie
if !found {
// HTTP request does not contain a corresponding cookie.
return nil, found, nil
}
if err != nil {
return nil, found, fmt.Errorf("decoding param %q: %w", param, err)
}
val, err := parsePrimitive(cookie.Value, schema)
return val, found, err
}
func (d *cookieParamDecoder) DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]interface{}, bool, error) {
if sm.Style != "form" || sm.Explode {
return nil, false, invalidSerializationMethodErr(sm)
}
cookie, err := d.req.Cookie(param)
found := err != http.ErrNoCookie
if !found {
// HTTP request does not contain a corresponding cookie.
return nil, found, nil
}
if err != nil {
return nil, found, fmt.Errorf("decoding param %q: %w", param, err)
}
val, err := parseArray(strings.Split(cookie.Value, ","), schema)
return val, found, err
}
func (d *cookieParamDecoder) DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, bool, error) {
if sm.Style != "form" || sm.Explode {
return nil, false, invalidSerializationMethodErr(sm)
}
cookie, err := d.req.Cookie(param)
found := err != http.ErrNoCookie
if !found {
// HTTP request does not contain a corresponding cookie.
return nil, found, nil
}
if err != nil {
return nil, found, fmt.Errorf("decoding param %q: %w", param, err)
}
props, err := propsFromString(cookie.Value, ",", ",")
if err != nil {
return nil, found, err
}
val, err := makeObject(props, schema)
return val, found, err
}
// propsFromString returns a properties map that is created by splitting a source string by propDelim and valueDelim.
// The source string must have a valid format: pairs separated by .
// The function returns an error when the source string has an invalid format.
func propsFromString(src, propDelim, valueDelim string) (map[string]string, error) {
props := make(map[string]string)
pairs := strings.Split(src, propDelim)
// When propDelim and valueDelim is equal the source string follow the next rule:
// every even item of pairs is a properties's name, and the subsequent odd item is a property's value.
if propDelim == valueDelim {
// Taking into account the rule above, a valid source string must be splitted by propDelim
// to an array with an even number of items.
if len(pairs)%2 != 0 {
return nil, &ParseError{
Kind: KindInvalidFormat,
Value: src,
Reason: fmt.Sprintf("a value must be a list of object's properties in format \"name%svalue\" separated by %s", valueDelim, propDelim),
}
}
for i := 0; i < len(pairs)/2; i++ {
props[pairs[i*2]] = pairs[i*2+1]
}
return props, nil
}
// When propDelim and valueDelim is not equal the source string follow the next rule:
// every item of pairs is a string that follows format .
for _, pair := range pairs {
prop := strings.Split(pair, valueDelim)
if len(prop) != 2 {
return nil, &ParseError{
Kind: KindInvalidFormat,
Value: src,
Reason: fmt.Sprintf("a value must be a list of object's properties in format \"name%svalue\" separated by %s", valueDelim, propDelim),
}
}
props[prop[0]] = prop[1]
}
return props, nil
}
func deepGet(m map[string]interface{}, keys ...string) (interface{}, bool) {
for _, key := range keys {
val, ok := m[key]
if !ok {
return nil, false
}
if m, ok = val.(map[string]interface{}); !ok {
return val, true
}
}
return m, true
}
func deepSet(m map[string]interface{}, keys []string, value interface{}) {
for i := 0; i < len(keys)-1; i++ {
key := keys[i]
if _, ok := m[key]; !ok {
m[key] = make(map[string]interface{})
}
m = m[key].(map[string]interface{})
}
m[keys[len(keys)-1]] = value
}
func findNestedSchema(parentSchema *openapi3.SchemaRef, keys []string) (*openapi3.SchemaRef, error) {
currentSchema := parentSchema
for _, key := range keys {
if currentSchema.Value.Type.Includes(openapi3.TypeArray) {
currentSchema = currentSchema.Value.Items
} else {
propertySchema, ok := currentSchema.Value.Properties[key]
if !ok {
if currentSchema.Value.AdditionalProperties.Schema == nil {
return nil, fmt.Errorf("nested schema for key %q not found", key)
}
currentSchema = currentSchema.Value.AdditionalProperties.Schema
continue
}
currentSchema = propertySchema
}
}
return currentSchema, nil
}
// makeObject returns an object that contains properties from props.
func makeObject(props map[string]string, schema *openapi3.SchemaRef) (map[string]interface{}, error) {
mobj := make(map[string]interface{})
for kk, value := range props {
keys := strings.Split(kk, urlDecoderDelimiter)
if strings.Contains(value, urlDecoderDelimiter) {
// don't support implicit array indexes anymore
p := pathFromKeys(keys)
return nil, &ParseError{path: p, Kind: KindInvalidFormat, Reason: "array items must be set with indexes"}
}
deepSet(mobj, keys, value)
}
r, err := buildResObj(mobj, nil, "", schema)
if err != nil {
return nil, err
}
result, ok := r.(map[string]interface{})
if !ok {
return nil, &ParseError{Kind: KindOther, Reason: "invalid param object", Value: result}
}
return result, nil
}
// example: map[0:map[key:true] 1:map[key:false]] -> [map[key:true] map[key:false]]
func sliceMapToSlice(m map[string]interface{}) ([]interface{}, error) {
var result []interface{}
keys := make([]int, 0, len(m))
for k := range m {
key, err := strconv.Atoi(k)
if err != nil {
return nil, fmt.Errorf("array indexes must be integers: %w", err)
}
keys = append(keys, key)
}
max := -1
for _, k := range keys {
if k > max {
max = k
}
}
for i := 0; i <= max; i++ {
val, ok := m[strconv.Itoa(i)]
if !ok {
result = append(result, nil)
continue
}
result = append(result, val)
}
return result, nil
}
// buildResObj constructs an object based on a given schema and param values
func buildResObj(params map[string]interface{}, parentKeys []string, key string, schema *openapi3.SchemaRef) (interface{}, error) {
mapKeys := parentKeys
if key != "" {
mapKeys = append(mapKeys, key)
}
switch {
case schema.Value.Type.Is("array"):
paramArr, ok := deepGet(params, mapKeys...)
if !ok {
return nil, nil
}
t, isMap := paramArr.(map[string]interface{})
if !isMap {
return nil, &ParseError{path: pathFromKeys(mapKeys), Kind: KindInvalidFormat, Reason: "array items must be set with indexes"}
}
// intermediate arrays have to be instantiated
arr, err := sliceMapToSlice(t)
if err != nil {
return nil, &ParseError{path: pathFromKeys(mapKeys), Kind: KindInvalidFormat, Reason: fmt.Sprintf("could not convert value map to array: %v", err)}
}
resultArr := make([]interface{} /*not 0,*/, len(arr))
for i := range arr {
r, err := buildResObj(params, mapKeys, strconv.Itoa(i), schema.Value.Items)
if err != nil {
return nil, err
}
if r != nil {
resultArr[i] = r
}
}
return resultArr, nil
case schema.Value.Type.Is("object"):
resultMap := make(map[string]interface{})
additPropsSchema := schema.Value.AdditionalProperties.Schema
pp, _ := deepGet(params, mapKeys...)
objectParams, ok := pp.(map[string]interface{})
if !ok {
// not the expected type, but return it either way and leave validation up to ValidateParameter
return pp, nil
}
for k, propSchema := range schema.Value.Properties {
r, err := buildResObj(params, mapKeys, k, propSchema)
if err != nil {
return nil, err
}
if r != nil {
resultMap[k] = r
}
}
if additPropsSchema != nil {
// dynamic creation of possibly nested objects
for k := range objectParams {
r, err := buildResObj(params, mapKeys, k, additPropsSchema)
if err != nil {
return nil, err
}
if r != nil {
resultMap[k] = r
}
}
}
return resultMap, nil
case len(schema.Value.AnyOf) > 0:
return buildFromSchemas(schema.Value.AnyOf, params, parentKeys, key)
case len(schema.Value.OneOf) > 0:
return buildFromSchemas(schema.Value.OneOf, params, parentKeys, key)
case len(schema.Value.AllOf) > 0:
return buildFromSchemas(schema.Value.AllOf, params, parentKeys, key)
default:
val, ok := deepGet(params, mapKeys...)
if !ok {
// leave validation up to ValidateParameter. here there really is not parameter set
return nil, nil
}
v, ok := val.(string)
if !ok {
return nil, &ParseError{path: pathFromKeys(mapKeys), Kind: KindInvalidFormat, Value: val, Reason: "path is not convertible to primitive"}
}
prim, err := parsePrimitive(v, schema)
if err != nil {
return nil, handlePropParseError(mapKeys, err)
}
return prim, nil
}
}
// buildFromSchemas decodes params with anyOf, oneOf, allOf schemas.
func buildFromSchemas(schemas openapi3.SchemaRefs, params map[string]interface{}, mapKeys []string, key string) (interface{}, error) {
resultMap := make(map[string]interface{})
for _, s := range schemas {
val, err := buildResObj(params, mapKeys, key, s)
if err == nil && val != nil {
if m, ok := val.(map[string]interface{}); ok {
for k, v := range m {
resultMap[k] = v
}
continue
}
if a, ok := val.([]interface{}); ok {
if len(a) > 0 {
return a, nil
}
continue
}
// if its a primitive and not nil just return that and let it be validated
return val, nil
}
}
if len(resultMap) > 0 {
return resultMap, nil
}
return nil, nil
}
func handlePropParseError(path []string, err error) error {
if v, ok := err.(*ParseError); ok {
return &ParseError{path: pathFromKeys(path), Cause: v}
}
return fmt.Errorf("property %q: %w", strings.Join(path, "."), err)
}
func pathFromKeys(kk []string) []interface{} {
path := make([]interface{}, 0, len(kk))
for _, v := range kk {
path = append(path, v)
}
return path
}
// parseArray returns an array that contains items from a raw array.
// Every item is parsed as a primitive value.
// The function returns an error when an error happened while parse array's items.
func parseArray(raw []string, schemaRef *openapi3.SchemaRef) ([]interface{}, error) {
var value []interface{}
for i, v := range raw {
item, err := parsePrimitive(v, schemaRef.Value.Items)
if err != nil {
if v, ok := err.(*ParseError); ok {
return nil, &ParseError{path: []interface{}{i}, Cause: v}
}
return nil, fmt.Errorf("item %d: %w", i, err)
}
// If the items are nil, then the array is nil. There shouldn't be case where some values are actual primitive
// values and some are nil values.
if item == nil {
return nil, nil
}
value = append(value, item)
}
return value, nil
}
// parsePrimitive returns a value that is created by parsing a source string to a primitive type
// that is specified by a schema. The function returns nil when the source string is empty.
// The function panics when a schema has a non-primitive type.
func parsePrimitive(raw string, schema *openapi3.SchemaRef) (v interface{}, err error) {
if raw == "" {
return nil, nil
}
for _, typ := range schema.Value.Type.Slice() {
if v, err = parsePrimitiveCase(raw, schema, typ); err == nil {
return
}
}
return
}
func parsePrimitiveCase(raw string, schema *openapi3.SchemaRef, typ string) (interface{}, error) {
switch typ {
case "integer":
if schema.Value.Format == "int32" {
v, err := strconv.ParseInt(raw, 0, 32)
if err != nil {
return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid " + typ, Cause: err.(*strconv.NumError).Err}
}
return int32(v), nil
}
v, err := strconv.ParseInt(raw, 0, 64)
if err != nil {
return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid " + typ, Cause: err.(*strconv.NumError).Err}
}
return v, nil
case "number":
v, err := strconv.ParseFloat(raw, 64)
if err != nil {
return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid " + typ, Cause: err.(*strconv.NumError).Err}
}
return v, nil
case "boolean":
v, err := strconv.ParseBool(raw)
if err != nil {
return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid " + typ, Cause: err.(*strconv.NumError).Err}
}
return v, nil
case "string":
return raw, nil
default:
return nil, &ParseError{Kind: KindOther, Value: raw, Reason: "schema has non primitive type " + typ}
}
}
// EncodingFn is a function that returns an encoding of a request body's part.
type EncodingFn func(partName string) *openapi3.Encoding
// BodyDecoder is an interface to decode a body of a request or response.
// An implementation must return a value that is a primitive, []interface{}, or map[string]interface{}.
type BodyDecoder func(io.Reader, http.Header, *openapi3.SchemaRef, EncodingFn) (interface{}, error)
// bodyDecoders contains decoders for supported content types of a body.
// By default, there is content type "application/json" is supported only.
var bodyDecoders = make(map[string]BodyDecoder)
// RegisteredBodyDecoder returns the registered body decoder for the given content type.
//
// If no decoder was registered for the given content type, nil is returned.
// This call is not thread-safe: body decoders should not be created/destroyed by multiple goroutines.
func RegisteredBodyDecoder(contentType string) BodyDecoder {
return bodyDecoders[contentType]
}
// RegisterBodyDecoder registers a request body's decoder for a content type.
//
// If a decoder for the specified content type already exists, the function replaces
// it with the specified decoder.
// This call is not thread-safe: body decoders should not be created/destroyed by multiple goroutines.
func RegisterBodyDecoder(contentType string, decoder BodyDecoder) {
if contentType == "" {
panic("contentType is empty")
}
if decoder == nil {
panic("decoder is not defined")
}
bodyDecoders[contentType] = decoder
}
// UnregisterBodyDecoder dissociates a body decoder from a content type.
//
// Decoding this content type will result in an error.
// This call is not thread-safe: body decoders should not be created/destroyed by multiple goroutines.
func UnregisterBodyDecoder(contentType string) {
if contentType == "" {
panic("contentType is empty")
}
delete(bodyDecoders, contentType)
}
var headerCT = http.CanonicalHeaderKey("Content-Type")
const prefixUnsupportedCT = "unsupported content type"
// decodeBody returns a decoded body.
// The function returns ParseError when a body is invalid.
func decodeBody(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (
string,
interface{},
error,
) {
contentType := header.Get(headerCT)
if contentType == "" {
if _, ok := body.(*multipart.Part); ok {
contentType = "text/plain"
}
}
mediaType := parseMediaType(contentType)
decoder, ok := bodyDecoders[mediaType]
if !ok {
return "", nil, &ParseError{
Kind: KindUnsupportedFormat,
Reason: fmt.Sprintf("%s %q", prefixUnsupportedCT, mediaType),
}
}
value, err := decoder(body, header, schema, encFn)
if err != nil {
return "", nil, err
}
return mediaType, value, nil
}
func init() {
RegisterBodyDecoder("application/json", JSONBodyDecoder)
RegisterBodyDecoder("application/json-patch+json", JSONBodyDecoder)
RegisterBodyDecoder("application/octet-stream", FileBodyDecoder)
RegisterBodyDecoder("application/problem+json", JSONBodyDecoder)
RegisterBodyDecoder("application/x-www-form-urlencoded", urlencodedBodyDecoder)
RegisterBodyDecoder("application/x-yaml", yamlBodyDecoder)
RegisterBodyDecoder("application/yaml", yamlBodyDecoder)
RegisterBodyDecoder("application/zip", zipFileBodyDecoder)
RegisterBodyDecoder("multipart/form-data", multipartBodyDecoder)
RegisterBodyDecoder("text/csv", csvBodyDecoder)
RegisterBodyDecoder("text/plain", plainBodyDecoder)
}
func plainBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) {
data, err := io.ReadAll(body)
if err != nil {
return nil, &ParseError{Kind: KindInvalidFormat, Cause: err}
}
return string(data), nil
}
// JSONBodyDecoder decodes a JSON formatted body. It is public so that is easy
// to register additional JSON based formats.
func JSONBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) {
var value interface{}
dec := json.NewDecoder(body)
dec.UseNumber()
if err := dec.Decode(&value); err != nil {
return nil, &ParseError{Kind: KindInvalidFormat, Cause: err}
}
return value, nil
}
func yamlBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) {
var value interface{}
if err := yaml.NewDecoder(body).Decode(&value); err != nil {
return nil, &ParseError{Kind: KindInvalidFormat, Cause: err}
}
return value, nil
}
func urlencodedBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) {
// Validate schema of request body.
// By the OpenAPI 3 specification request body's schema must have type "object".
// Properties of the schema describes individual parts of request body.
if !schema.Value.Type.Is("object") {
return nil, errors.New("unsupported schema of request body")
}
for propName, propSchema := range schema.Value.Properties {
propType := propSchema.Value.Type
switch {
case propType.Is("object"):
return nil, fmt.Errorf("unsupported schema of request body's property %q", propName)
case propType.Is("array"):
items := propSchema.Value.Items.Value
if !(items.Type.Is("string") || items.Type.Is("integer") || items.Type.Is("number") || items.Type.Is("boolean")) {
return nil, fmt.Errorf("unsupported schema of request body's property %q", propName)
}
}
}
// Parse form.
b, err := io.ReadAll(body)
if err != nil {
return nil, err
}
values, err := url.ParseQuery(string(b))
if err != nil {
return nil, err
}
// Make an object value from form values.
obj := make(map[string]interface{})
dec := &urlValuesDecoder{values: values}
// Decode schema constructs (allOf, anyOf, oneOf)
if err := decodeSchemaConstructs(dec, schema.Value.AllOf, obj, encFn); err != nil {
return nil, err
}
if err := decodeSchemaConstructs(dec, schema.Value.AnyOf, obj, encFn); err != nil {
return nil, err
}
if err := decodeSchemaConstructs(dec, schema.Value.OneOf, obj, encFn); err != nil {
return nil, err
}
// Decode properties from the main schema
if err := decodeSchemaConstructs(dec, []*openapi3.SchemaRef{schema}, obj, encFn); err != nil {
return nil, err
}
return obj, nil
}
// decodeSchemaConstructs tries to decode properties based on provided schemas.
// This function is for decoding purposes only and not for validation.
func decodeSchemaConstructs(dec *urlValuesDecoder, schemas []*openapi3.SchemaRef, obj map[string]interface{}, encFn EncodingFn) error {
for _, schemaRef := range schemas {
for name, prop := range schemaRef.Value.Properties {
value, _, err := decodeProperty(dec, name, prop, encFn)
if err != nil {
continue
}
if existingValue, exists := obj[name]; exists && !isEqual(existingValue, value) {
return fmt.Errorf("conflicting values for property %q", name)
}
obj[name] = value
}
}
return nil
}
func isEqual(value1, value2 interface{}) bool {
return reflect.DeepEqual(value1, value2)
}
func decodeProperty(dec valueDecoder, name string, prop *openapi3.SchemaRef, encFn EncodingFn) (interface{}, bool, error) {
var enc *openapi3.Encoding
if encFn != nil {
enc = encFn(name)
}
sm := enc.SerializationMethod()
return decodeValue(dec, name, sm, prop, false)
}
func multipartBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) {
if !schema.Value.Type.Is("object") {
return nil, errors.New("unsupported schema of request body")
}
// Parse form.
values := make(map[string][]interface{})
contentType := header.Get(headerCT)
_, params, err := mime.ParseMediaType(contentType)
if err != nil {
return nil, err
}
mr := multipart.NewReader(body, params["boundary"])
for {
var part *multipart.Part
if part, err = mr.NextPart(); err == io.EOF {
break
}
if err != nil {
return nil, err
}
var (
name = part.FormName()
enc *openapi3.Encoding
)
if encFn != nil {
enc = encFn(name)
}
subEncFn := func(string) *openapi3.Encoding { return enc }
var valueSchema *openapi3.SchemaRef
if len(schema.Value.AllOf) > 0 {
var exists bool
for _, sr := range schema.Value.AllOf {
if valueSchema, exists = sr.Value.Properties[name]; exists {
break
}
}
if !exists {
return nil, &ParseError{Kind: KindOther, Cause: fmt.Errorf("part %s: undefined", name)}
}
} else {
// If the property's schema has type "array" it is means that the form contains a few parts with the same name.
// Every such part has a type that is defined by an items schema in the property's schema.
var exists bool
if valueSchema, exists = schema.Value.Properties[name]; !exists {
if anyProperties := schema.Value.AdditionalProperties.Has; anyProperties != nil {
switch *anyProperties {
case true:
// additionalProperties: true
continue
default:
// additionalProperties: false
return nil, &ParseError{Kind: KindOther, Cause: fmt.Errorf("part %s: undefined", name)}
}
}
if schema.Value.AdditionalProperties.Schema == nil {
return nil, &ParseError{Kind: KindOther, Cause: fmt.Errorf("part %s: undefined", name)}
}
if valueSchema, exists = schema.Value.AdditionalProperties.Schema.Value.Properties[name]; !exists {
return nil, &ParseError{Kind: KindOther, Cause: fmt.Errorf("part %s: undefined", name)}
}
}
if valueSchema.Value.Type.Is("array") {
valueSchema = valueSchema.Value.Items
}
}
var value interface{}
if _, value, err = decodeBody(part, http.Header(part.Header), valueSchema, subEncFn); err != nil {
if v, ok := err.(*ParseError); ok {
return nil, &ParseError{path: []interface{}{name}, Cause: v}
}
return nil, fmt.Errorf("part %s: %w", name, err)
}
values[name] = append(values[name], value)
}
allTheProperties := make(map[string]*openapi3.SchemaRef)
if len(schema.Value.AllOf) > 0 {
for _, sr := range schema.Value.AllOf {
for k, v := range sr.Value.Properties {
allTheProperties[k] = v
}
if addProps := sr.Value.AdditionalProperties.Schema; addProps != nil {
for k, v := range addProps.Value.Properties {
allTheProperties[k] = v
}
}
}
} else {
for k, v := range schema.Value.Properties {
allTheProperties[k] = v
}
if addProps := schema.Value.AdditionalProperties.Schema; addProps != nil {
for k, v := range addProps.Value.Properties {
allTheProperties[k] = v
}
}
}
// Make an object value from form values.
obj := make(map[string]interface{})
for name, prop := range allTheProperties {
vv := values[name]
if len(vv) == 0 {
continue
}
if prop.Value.Type.Is("array") {
obj[name] = vv
} else {
obj[name] = vv[0]
}
}
return obj, nil
}
// FileBodyDecoder is a body decoder that decodes a file body to a string.
func FileBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) {
data, err := io.ReadAll(body)
if err != nil {
return nil, err
}
return string(data), nil
}
// zipFileBodyDecoder is a body decoder that decodes a zip file body to a string.
func zipFileBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) {
buff := bytes.NewBuffer([]byte{})
size, err := io.Copy(buff, body)
if err != nil {
return nil, err
}
zr, err := zip.NewReader(bytes.NewReader(buff.Bytes()), size)
if err != nil {
return nil, err
}
const bufferSize = 256
content := make([]byte, 0, bufferSize*len(zr.File))
buffer := make([]byte /*0,*/, bufferSize)
for _, f := range zr.File {
err := func() error {
rc, err := f.Open()
if err != nil {
return err
}
defer func() {
_ = rc.Close()
}()
for {
n, err := rc.Read(buffer)
if 0 < n {
content = append(content, buffer...)
}
if err == io.EOF {
break
}
if err != nil {
return err
}
}
return nil
}()
if err != nil {
return nil, err
}
}
return string(content), nil
}
// csvBodyDecoder is a body decoder that decodes a csv body to a string.
func csvBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) {
r := csv.NewReader(body)
var content string
for {
record, err := r.Read()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
content += strings.Join(record, ",") + "\n"
}
return content, nil
}
kin-openapi-0.124.0/openapi3filter/req_resp_decoder_test.go 0000664 0000000 0000000 00000172215 14604223742 0023723 0 ustar 00root root 0000000 0000000 package openapi3filter
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/textproto"
"net/url"
"reflect"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
legacyrouter "github.com/getkin/kin-openapi/routers/legacy"
)
var (
explode = openapi3.BoolPtr(true)
noExplode = openapi3.BoolPtr(false)
arrayOf = func(items *openapi3.SchemaRef) *openapi3.SchemaRef {
return &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"array"}, Items: items}}
}
objectOf = func(args ...interface{}) *openapi3.SchemaRef {
s := &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"object"}, Properties: make(map[string]*openapi3.SchemaRef)}}
if len(args)%2 != 0 {
panic("invalid arguments. must be an even number of arguments")
}
for i := 0; i < len(args)/2; i++ {
propName := args[i*2].(string)
propSchema := args[i*2+1].(*openapi3.SchemaRef)
s.Value.Properties[propName] = propSchema
}
return s
}
additionalPropertiesObjectOf = func(schema *openapi3.SchemaRef) *openapi3.SchemaRef {
return &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"object"}, AdditionalProperties: openapi3.AdditionalProperties{Schema: schema}}}
}
integerSchema = &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"integer"}}}
numberSchema = &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"number"}}}
booleanSchema = &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"boolean"}}}
stringSchema = &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}}
additionalPropertiesObjectStringSchema = &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"object"}, AdditionalProperties: openapi3.AdditionalProperties{Schema: stringSchema}}}
additionalPropertiesObjectBoolSchema = &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"object"}, AdditionalProperties: openapi3.AdditionalProperties{Schema: booleanSchema}}}
allofSchema = &openapi3.SchemaRef{
Value: &openapi3.Schema{
AllOf: []*openapi3.SchemaRef{
integerSchema,
numberSchema,
},
},
}
anyofSchema = &openapi3.SchemaRef{
Value: &openapi3.Schema{
AnyOf: []*openapi3.SchemaRef{
integerSchema,
stringSchema,
},
},
}
oneofSchema = &openapi3.SchemaRef{
Value: &openapi3.Schema{
OneOf: []*openapi3.SchemaRef{
booleanSchema,
integerSchema,
},
},
}
oneofSchemaObject = &openapi3.SchemaRef{
Value: &openapi3.Schema{
OneOf: []*openapi3.SchemaRef{
objectOneRSchema,
objectTwoRSchema,
},
},
}
anyofSchemaObject = &openapi3.SchemaRef{
Value: &openapi3.Schema{
AnyOf: []*openapi3.SchemaRef{
objectOneRSchema,
objectTwoRSchema,
},
},
}
stringArraySchema = arrayOf(stringSchema)
integerArraySchema = arrayOf(integerSchema)
objectSchema = objectOf("id", stringSchema, "name", stringSchema)
objectTwoRSchema = func() *openapi3.SchemaRef {
s := objectOf("id2", stringSchema, "name2", stringSchema)
s.Value.Required = []string{"id2"}
return s
}()
objectOneRSchema = func() *openapi3.SchemaRef {
s := objectOf("id", stringSchema, "name", stringSchema)
s.Value.Required = []string{"id"}
return s
}()
oneofSchemaArrayObject = &openapi3.SchemaRef{
Value: &openapi3.Schema{
AnyOf: []*openapi3.SchemaRef{
stringArraySchema,
objectTwoRSchema,
},
},
}
)
func TestDeepGet(t *testing.T) {
iarray := map[string]interface{}{
"0": map[string]interface{}{
"foo": 111,
},
"1": map[string]interface{}{
"bar": 222,
},
}
tests := []struct {
name string
m map[string]interface{}
keys []string
expected interface{}
shouldFind bool
}{
{
name: "Simple map - key exists",
m: map[string]interface{}{
"foo": "bar",
},
keys: []string{"foo"},
expected: "bar",
shouldFind: true,
},
{
name: "Nested map - key exists",
m: map[string]interface{}{
"foo": map[string]interface{}{
"bar": "baz",
},
},
keys: []string{"foo", "bar"},
expected: "baz",
shouldFind: true,
},
{
name: "Nested map - key does not exist",
m: map[string]interface{}{
"foo": map[string]interface{}{
"bar": "baz",
},
},
keys: []string{"foo", "baz"},
expected: nil,
shouldFind: false,
},
{
name: "Array - element exists",
m: map[string]interface{}{
"array": map[string]interface{}{"0": "a", "1": "b", "2": "c"},
},
keys: []string{"array", "1"},
expected: "b",
shouldFind: true,
},
{
name: "Array - element does not exist - invalid index",
m: map[string]interface{}{
"array": map[string]interface{}{"0": "a", "1": "b", "2": "c"},
},
keys: []string{"array", "3"},
expected: nil,
shouldFind: false,
},
{
name: "Array - element does not exist - invalid keys",
m: map[string]interface{}{
"array": map[string]interface{}{"0": "a", "1": "b", "2": "c"},
},
keys: []string{"array", "a", "999"},
expected: nil,
shouldFind: false,
},
{
name: "Array of objects - element exists 1",
m: map[string]interface{}{
"array": iarray,
},
keys: []string{"array", "1", "bar"},
expected: 222,
shouldFind: true,
},
{
name: "Array of objects - element exists 2",
m: map[string]interface{}{
"array": iarray,
},
keys: []string{"array", "0"},
expected: map[string]interface{}{
"foo": 111,
},
shouldFind: true,
},
{
name: "Array of objects - element exists 3",
m: map[string]interface{}{
"array": iarray,
},
keys: []string{"array"},
expected: iarray,
shouldFind: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
tc := tc
result, found := deepGet(tc.m, tc.keys...)
require.Equal(t, tc.shouldFind, found, "shouldFind mismatch")
require.Equal(t, tc.expected, result, "result mismatch")
})
}
}
func TestDeepSet(t *testing.T) {
tests := []struct {
name string
inputMap map[string]interface{}
keys []string
value interface{}
expected map[string]interface{}
}{
{
name: "simple set",
inputMap: map[string]interface{}{},
keys: []string{"key"},
value: "value",
expected: map[string]interface{}{"key": "value"},
},
{
name: "intermediate array of objects",
inputMap: map[string]interface{}{},
keys: []string{"nested", "0", "key"},
value: true,
expected: map[string]interface{}{
"nested": map[string]interface{}{
"0": map[string]interface{}{
"key": true,
},
},
},
},
{
name: "existing nested array of objects",
inputMap: map[string]interface{}{"nested": map[string]interface{}{"0": map[string]interface{}{"existingKey": "existingValue"}}},
keys: []string{"nested", "0", "newKey"},
value: "newValue",
expected: map[string]interface{}{
"nested": map[string]interface{}{
"0": map[string]interface{}{
"existingKey": "existingValue",
"newKey": "newValue",
},
},
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
deepSet(tc.inputMap, tc.keys, tc.value)
require.EqualValues(t, tc.expected, tc.inputMap)
})
}
}
func TestDecodeParameter(t *testing.T) {
type testCase struct {
name string
param *openapi3.Parameter
path string
query string
header string
cookie string
want interface{}
found bool
err error
}
testGroups := []struct {
name string
testCases []testCase
}{
{
name: "path primitive",
testCases: []testCase{
{
name: "simple",
param: &openapi3.Parameter{Name: "param", In: "path", Style: "simple", Explode: noExplode, Schema: stringSchema},
path: "/foo",
want: "foo",
found: true,
},
{
name: "simple explode",
param: &openapi3.Parameter{Name: "param", In: "path", Style: "simple", Explode: explode, Schema: stringSchema},
path: "/foo",
want: "foo",
found: true,
},
{
name: "label",
param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: noExplode, Schema: stringSchema},
path: "/.foo",
want: "foo",
found: true,
},
{
name: "label invalid",
param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: noExplode, Schema: stringSchema},
path: "/foo",
found: true,
err: &ParseError{Kind: KindInvalidFormat, Value: "foo"},
},
{
name: "label explode",
param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: explode, Schema: stringSchema},
path: "/.foo",
want: "foo",
found: true,
},
{
name: "label explode invalid",
param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: explode, Schema: stringSchema},
path: "/foo",
found: true,
err: &ParseError{Kind: KindInvalidFormat, Value: "foo"},
},
{
name: "matrix",
param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: noExplode, Schema: stringSchema},
path: "/;param=foo",
want: "foo",
found: true,
},
{
name: "matrix invalid",
param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: noExplode, Schema: stringSchema},
path: "/foo",
found: true,
err: &ParseError{Kind: KindInvalidFormat, Value: "foo"},
},
{
name: "matrix explode",
param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: explode, Schema: stringSchema},
path: "/;param=foo",
want: "foo",
found: true,
},
{
name: "matrix explode invalid",
param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: explode, Schema: stringSchema},
path: "/foo",
found: true,
err: &ParseError{Kind: KindInvalidFormat, Value: "foo"},
},
{
name: "default",
param: &openapi3.Parameter{Name: "param", In: "path", Schema: stringSchema},
path: "/foo",
want: "foo",
found: true,
},
{
name: "string",
param: &openapi3.Parameter{Name: "param", In: "path", Schema: stringSchema},
path: "/foo",
want: "foo",
found: true,
},
{
name: "integer",
param: &openapi3.Parameter{Name: "param", In: "path", Schema: integerSchema},
path: "/1",
want: int64(1),
found: true,
},
{
name: "integer invalid",
param: &openapi3.Parameter{Name: "param", In: "path", Schema: integerSchema},
path: "/foo",
found: true,
err: &ParseError{Kind: KindInvalidFormat, Value: "foo"},
},
{
name: "number",
param: &openapi3.Parameter{Name: "param", In: "path", Schema: numberSchema},
path: "/1.1",
want: 1.1,
found: true,
},
{
name: "number invalid",
param: &openapi3.Parameter{Name: "param", In: "path", Schema: numberSchema},
path: "/foo",
found: true,
err: &ParseError{Kind: KindInvalidFormat, Value: "foo"},
},
{
name: "boolean",
param: &openapi3.Parameter{Name: "param", In: "path", Schema: booleanSchema},
path: "/true",
want: true,
found: true,
},
{
name: "boolean invalid",
param: &openapi3.Parameter{Name: "param", In: "path", Schema: booleanSchema},
path: "/foo",
found: true,
err: &ParseError{Kind: KindInvalidFormat, Value: "foo"},
},
},
},
{
name: "path array",
testCases: []testCase{
{
name: "simple",
param: &openapi3.Parameter{Name: "param", In: "path", Style: "simple", Explode: noExplode, Schema: stringArraySchema},
path: "/foo,bar",
want: []interface{}{"foo", "bar"},
found: true,
},
{
name: "simple explode",
param: &openapi3.Parameter{Name: "param", In: "path", Style: "simple", Explode: explode, Schema: stringArraySchema},
path: "/foo,bar",
want: []interface{}{"foo", "bar"},
found: true,
},
{
name: "label",
param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: noExplode, Schema: stringArraySchema},
path: "/.foo,bar",
want: []interface{}{"foo", "bar"},
found: true,
},
{
name: "label invalid",
param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: noExplode, Schema: stringArraySchema},
path: "/foo,bar",
found: true,
err: &ParseError{Kind: KindInvalidFormat, Value: "foo,bar"},
},
{
name: "label explode",
param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: explode, Schema: stringArraySchema},
path: "/.foo.bar",
want: []interface{}{"foo", "bar"},
found: true,
},
{
name: "label explode invalid",
param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: explode, Schema: stringArraySchema},
path: "/foo.bar",
found: true,
err: &ParseError{Kind: KindInvalidFormat, Value: "foo.bar"},
},
{
name: "matrix",
param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: noExplode, Schema: stringArraySchema},
path: "/;param=foo,bar",
want: []interface{}{"foo", "bar"},
found: true,
},
{
name: "matrix invalid",
param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: noExplode, Schema: stringArraySchema},
path: "/foo,bar",
found: true,
err: &ParseError{Kind: KindInvalidFormat, Value: "foo,bar"},
},
{
name: "matrix explode",
param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: explode, Schema: stringArraySchema},
path: "/;param=foo;param=bar",
want: []interface{}{"foo", "bar"},
found: true,
},
{
name: "matrix explode invalid",
param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: explode, Schema: stringArraySchema},
path: "/foo,bar",
found: true,
err: &ParseError{Kind: KindInvalidFormat, Value: "foo,bar"},
},
{
name: "default",
param: &openapi3.Parameter{Name: "param", In: "path", Schema: stringArraySchema},
path: "/foo,bar",
want: []interface{}{"foo", "bar"},
found: true,
},
{
name: "invalid integer items",
param: &openapi3.Parameter{Name: "param", In: "path", Schema: arrayOf(integerSchema)},
path: "/1,foo",
found: true,
err: &ParseError{path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "foo"}},
},
{
name: "invalid number items",
param: &openapi3.Parameter{Name: "param", In: "path", Schema: arrayOf(numberSchema)},
path: "/1.1,foo",
found: true,
err: &ParseError{path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "foo"}},
},
{
name: "invalid boolean items",
param: &openapi3.Parameter{Name: "param", In: "path", Schema: arrayOf(booleanSchema)},
path: "/true,foo",
found: true,
err: &ParseError{path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "foo"}},
},
},
},
{
name: "path object",
testCases: []testCase{
{
name: "simple",
param: &openapi3.Parameter{Name: "param", In: "path", Style: "simple", Explode: noExplode, Schema: objectSchema},
path: "/id,foo,name,bar",
want: map[string]interface{}{"id": "foo", "name": "bar"},
found: true,
},
{
name: "simple explode",
param: &openapi3.Parameter{Name: "param", In: "path", Style: "simple", Explode: explode, Schema: objectSchema},
path: "/id=foo,name=bar",
want: map[string]interface{}{"id": "foo", "name": "bar"},
found: true,
},
{
name: "label",
param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: noExplode, Schema: objectSchema},
path: "/.id,foo,name,bar",
want: map[string]interface{}{"id": "foo", "name": "bar"},
found: true,
},
{
name: "label invalid",
param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: noExplode, Schema: objectSchema},
path: "/id,foo,name,bar",
found: true,
err: &ParseError{Kind: KindInvalidFormat, Value: "id,foo,name,bar"},
},
{
name: "label explode",
param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: explode, Schema: objectSchema},
path: "/.id=foo.name=bar",
want: map[string]interface{}{"id": "foo", "name": "bar"},
found: true,
},
{
name: "label explode invalid",
param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: explode, Schema: objectSchema},
path: "/id=foo.name=bar",
found: true,
err: &ParseError{Kind: KindInvalidFormat, Value: "id=foo.name=bar"},
},
{
name: "matrix",
param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: noExplode, Schema: objectSchema},
path: "/;param=id,foo,name,bar",
want: map[string]interface{}{"id": "foo", "name": "bar"},
found: true,
},
{
name: "matrix invalid",
param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: noExplode, Schema: objectSchema},
path: "/id,foo,name,bar",
found: true,
err: &ParseError{Kind: KindInvalidFormat, Value: "id,foo,name,bar"},
},
{
name: "matrix explode",
param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: explode, Schema: objectSchema},
path: "/;id=foo;name=bar",
want: map[string]interface{}{"id": "foo", "name": "bar"},
found: true,
},
{
name: "matrix explode invalid",
param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: explode, Schema: objectSchema},
path: "/id=foo;name=bar",
found: true,
err: &ParseError{Kind: KindInvalidFormat, Value: "id=foo;name=bar"},
},
{
name: "default",
param: &openapi3.Parameter{Name: "param", In: "path", Schema: objectSchema},
path: "/id,foo,name,bar",
want: map[string]interface{}{"id": "foo", "name": "bar"},
found: true,
},
{
name: "invalid integer prop",
param: &openapi3.Parameter{Name: "param", In: "path", Schema: objectOf("foo", integerSchema)},
path: "/foo,bar",
found: true,
err: &ParseError{path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "bar"}},
},
{
name: "invalid number prop",
param: &openapi3.Parameter{Name: "param", In: "path", Schema: objectOf("foo", numberSchema)},
path: "/foo,bar",
found: true,
err: &ParseError{path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "bar"}},
},
{
name: "invalid boolean prop",
param: &openapi3.Parameter{Name: "param", In: "path", Schema: objectOf("foo", booleanSchema)},
path: "/foo,bar",
found: true,
err: &ParseError{path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "bar"}},
},
},
},
{
name: "query primitive",
testCases: []testCase{
{
name: "form",
param: &openapi3.Parameter{Name: "param", In: "query", Style: "form", Explode: noExplode, Schema: stringSchema},
query: "param=foo",
want: "foo",
found: true,
},
{
name: "form explode",
param: &openapi3.Parameter{Name: "param", In: "query", Style: "form", Explode: explode, Schema: stringSchema},
query: "param=foo",
want: "foo",
found: true,
},
{
name: "default",
param: &openapi3.Parameter{Name: "param", In: "query", Schema: stringSchema},
query: "param=foo",
want: "foo",
found: true,
},
{
name: "string",
param: &openapi3.Parameter{Name: "param", In: "query", Schema: stringSchema},
query: "param=foo",
want: "foo",
found: true,
},
{
name: "integer",
param: &openapi3.Parameter{Name: "param", In: "query", Schema: integerSchema},
query: "param=1",
want: int64(1),
found: true,
},
{
name: "integer invalid",
param: &openapi3.Parameter{Name: "param", In: "query", Schema: integerSchema},
query: "param=foo",
found: true,
err: &ParseError{Kind: KindInvalidFormat, Value: "foo"},
},
{
name: "number",
param: &openapi3.Parameter{Name: "param", In: "query", Schema: numberSchema},
query: "param=1.1",
want: 1.1,
found: true,
},
{
name: "number invalid",
param: &openapi3.Parameter{Name: "param", In: "query", Schema: numberSchema},
query: "param=foo",
found: true,
err: &ParseError{Kind: KindInvalidFormat, Value: "foo"},
},
{
name: "boolean",
param: &openapi3.Parameter{Name: "param", In: "query", Schema: booleanSchema},
query: "param=true",
want: true,
found: true,
},
{
name: "boolean invalid",
param: &openapi3.Parameter{Name: "param", In: "query", Schema: booleanSchema},
query: "param=foo",
found: true,
err: &ParseError{Kind: KindInvalidFormat, Value: "foo"},
},
},
},
{
name: "query Allof",
testCases: []testCase{
{
name: "allofSchema integer and number",
param: &openapi3.Parameter{Name: "param", In: "query", Schema: allofSchema},
query: "param=1",
want: float64(1),
found: true,
},
{
name: "allofSchema string",
param: &openapi3.Parameter{Name: "param", In: "query", Schema: allofSchema},
query: "param=abdf",
found: true,
err: &ParseError{Kind: KindInvalidFormat, Value: "abdf"},
},
},
},
{
name: "query Anyof",
testCases: []testCase{
{
name: "anyofSchema integer",
param: &openapi3.Parameter{Name: "param", In: "query", Schema: anyofSchema},
query: "param=1",
want: int64(1),
found: true,
},
{
name: "anyofSchema string",
param: &openapi3.Parameter{Name: "param", In: "query", Schema: anyofSchema},
query: "param=abdf",
want: "abdf",
found: true,
},
},
},
{
name: "query Oneof",
testCases: []testCase{
{
name: "oneofSchema boolean",
param: &openapi3.Parameter{Name: "param", In: "query", Schema: oneofSchema},
query: "param=true",
want: true,
found: true,
},
{
name: "oneofSchema int",
param: &openapi3.Parameter{Name: "param", In: "query", Schema: oneofSchema},
query: "param=1122",
want: int64(1122),
found: true,
},
{
name: "oneofSchema string",
param: &openapi3.Parameter{Name: "param", In: "query", Schema: oneofSchema},
query: "param=abcd",
want: nil,
found: true,
},
},
},
{
name: "query array",
testCases: []testCase{
{
name: "form",
param: &openapi3.Parameter{Name: "param", In: "query", Style: "form", Explode: noExplode, Schema: stringArraySchema},
query: "param=foo,bar",
want: []interface{}{"foo", "bar"},
found: true,
},
{
name: "form explode",
param: &openapi3.Parameter{Name: "param", In: "query", Style: "form", Explode: explode, Schema: stringArraySchema},
query: "param=foo¶m=bar",
want: []interface{}{"foo", "bar"},
found: true,
},
{
name: "spaceDelimited",
param: &openapi3.Parameter{Name: "param", In: "query", Style: "spaceDelimited", Explode: noExplode, Schema: stringArraySchema},
query: "param=foo bar",
want: []interface{}{"foo", "bar"},
found: true,
},
{
name: "spaceDelimited explode",
param: &openapi3.Parameter{Name: "param", In: "query", Style: "spaceDelimited", Explode: explode, Schema: stringArraySchema},
query: "param=foo¶m=bar",
want: []interface{}{"foo", "bar"},
found: true,
},
{
name: "pipeDelimited",
param: &openapi3.Parameter{Name: "param", In: "query", Style: "pipeDelimited", Explode: noExplode, Schema: stringArraySchema},
query: "param=foo|bar",
want: []interface{}{"foo", "bar"},
found: true,
},
{
name: "pipeDelimited explode",
param: &openapi3.Parameter{Name: "param", In: "query", Style: "pipeDelimited", Explode: explode, Schema: stringArraySchema},
query: "param=foo¶m=bar",
want: []interface{}{"foo", "bar"},
found: true,
},
{
name: "default",
param: &openapi3.Parameter{Name: "param", In: "query", Schema: stringArraySchema},
query: "param=foo¶m=bar",
want: []interface{}{"foo", "bar"},
found: true,
},
{
name: "invalid integer items",
param: &openapi3.Parameter{Name: "param", In: "query", Schema: arrayOf(integerSchema)},
query: "param=1¶m=foo",
found: true,
err: &ParseError{path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "foo"}},
},
{
name: "invalid number items",
param: &openapi3.Parameter{Name: "param", In: "query", Schema: arrayOf(numberSchema)},
query: "param=1.1¶m=foo",
found: true,
err: &ParseError{path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "foo"}},
},
{
name: "invalid boolean items",
param: &openapi3.Parameter{Name: "param", In: "query", Schema: arrayOf(booleanSchema)},
query: "param=true¶m=foo",
found: true,
err: &ParseError{path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "foo"}},
},
},
},
{
name: "query object",
testCases: []testCase{
{
name: "form",
param: &openapi3.Parameter{Name: "param", In: "query", Style: "form", Explode: noExplode, Schema: objectSchema},
query: "param=id,foo,name,bar",
want: map[string]interface{}{"id": "foo", "name": "bar"},
found: true,
},
{
name: "form explode",
param: &openapi3.Parameter{Name: "param", In: "query", Style: "form", Explode: explode, Schema: objectSchema},
query: "id=foo&name=bar",
want: map[string]interface{}{"id": "foo", "name": "bar"},
found: true,
},
{
name: "deepObject explode",
param: &openapi3.Parameter{Name: "param", In: "query", Style: "deepObject", Explode: explode, Schema: objectSchema},
query: "param[id]=foo¶m[name]=bar",
want: map[string]interface{}{"id": "foo", "name": "bar"},
found: true,
},
{
name: "deepObject explode additionalProperties with object properties - missing index on nested array",
param: &openapi3.Parameter{
Name: "param", In: "query", Style: "deepObject", Explode: explode,
Schema: objectOf(
"obj", additionalPropertiesObjectOf(objectOf("item1", integerSchema, "item2", stringArraySchema)),
"objIgnored", objectOf("items", stringArraySchema),
),
},
query: "param[obj][prop2][item2]=def",
err: &ParseError{path: []interface{}{"obj", "prop2", "item2"}, Kind: KindInvalidFormat, Reason: "array items must be set with indexes"},
},
{
name: "deepObject explode array - missing indexes",
param: &openapi3.Parameter{Name: "param", In: "query", Style: "deepObject", Explode: explode, Schema: objectOf("items", stringArraySchema)},
query: "param[items]=f%26oo¶m[items]=bar",
found: true,
err: &ParseError{path: []interface{}{"items"}, Kind: KindInvalidFormat, Reason: "array items must be set with indexes"},
},
{
name: "deepObject explode array",
param: &openapi3.Parameter{Name: "param", In: "query", Style: "deepObject", Explode: explode, Schema: objectOf("items", integerArraySchema)},
query: "param[items][1]=456¶m[items][0]=123",
want: map[string]interface{}{"items": []interface{}{int64(123), int64(456)}},
found: true,
},
{
name: "deepObject explode nested object additionalProperties",
param: &openapi3.Parameter{
Name: "param", In: "query", Style: "deepObject", Explode: explode,
Schema: objectOf(
"obj", additionalPropertiesObjectStringSchema,
"objTwo", stringSchema,
"objIgnored", objectOf("items", stringArraySchema),
),
},
query: "param[obj][prop1]=bar¶m[obj][prop2]=foo¶m[objTwo]=string",
want: map[string]interface{}{
"obj": map[string]interface{}{"prop1": "bar", "prop2": "foo"},
"objTwo": "string",
},
found: true,
},
{
name: "deepObject explode additionalProperties with object properties - sharing property",
param: &openapi3.Parameter{
Name: "param", In: "query", Style: "deepObject", Explode: explode,
Schema: objectOf(
"obj", additionalPropertiesObjectOf(objectOf("item1", integerSchema, "item2", stringSchema)),
"objIgnored", objectOf("items", stringArraySchema),
),
},
query: "param[obj][prop1][item1]=1¶m[obj][prop1][item2]=abc",
want: map[string]interface{}{
"obj": map[string]interface{}{"prop1": map[string]interface{}{
"item1": int64(1),
"item2": "abc",
}},
},
found: true,
},
{
name: "deepObject explode nested object additionalProperties - bad value",
param: &openapi3.Parameter{
Name: "param", In: "query", Style: "deepObject", Explode: explode,
Schema: objectOf(
"obj", additionalPropertiesObjectBoolSchema,
"objTwo", stringSchema,
"objIgnored", objectOf("items", stringArraySchema),
),
},
query: "param[obj][prop1]=notbool¶m[objTwo]=string",
err: &ParseError{path: []interface{}{"obj", "prop1"}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "notbool"}},
},
{
name: "deepObject explode nested object additionalProperties - bad index inside additionalProperties",
param: &openapi3.Parameter{
Name: "param", In: "query", Style: "deepObject", Explode: explode,
Schema: objectOf(
"obj", additionalPropertiesObjectStringSchema,
"objTwo", stringSchema,
"objIgnored", objectOf("items", stringArraySchema),
),
},
query: "param[obj][prop1]=bar¶m[obj][prop2][badindex]=bad¶m[objTwo]=string",
err: &ParseError{
path: []interface{}{"obj", "prop2"},
Reason: `path is not convertible to primitive`,
Kind: KindInvalidFormat,
Value: map[string]interface{}{"badindex": "bad"},
},
},
{
name: "deepObject explode nested object",
param: &openapi3.Parameter{
Name: "param", In: "query", Style: "deepObject", Explode: explode,
Schema: objectOf(
"obj", objectOf("nestedObjOne", stringSchema, "nestedObjTwo", stringSchema),
"objTwo", stringSchema,
"objIgnored", objectOf("items", stringArraySchema),
),
},
query: "param[obj][nestedObjOne]=bar¶m[obj][nestedObjTwo]=foo¶m[objTwo]=string",
want: map[string]interface{}{
"obj": map[string]interface{}{"nestedObjOne": "bar", "nestedObjTwo": "foo"},
"objTwo": "string",
},
found: true,
},
{
name: "deepObject explode nested object - extraneous param ignored",
param: &openapi3.Parameter{
Name: "param", In: "query", Style: "deepObject", Explode: explode,
Schema: objectOf(
"obj", objectOf("nestedObjOne", stringSchema, "nestedObjTwo", stringSchema),
),
},
query: "anotherparam=bar",
want: map[string]interface{}(nil),
},
{
name: "deepObject explode nested object - bad array item type",
param: &openapi3.Parameter{
Name: "param", In: "query", Style: "deepObject", Explode: explode,
Schema: objectOf(
"objTwo", integerArraySchema,
),
},
query: "param[objTwo][0]=badint",
err: &ParseError{path: []interface{}{"objTwo", "0"}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "badint"}},
},
{
name: "deepObject explode deeply nested object - bad array item type",
param: &openapi3.Parameter{
Name: "param", In: "query", Style: "deepObject", Explode: explode,
Schema: objectOf(
"obj", objectOf("nestedObjOne", integerArraySchema),
),
},
query: "param[obj][nestedObjOne][0]=badint",
err: &ParseError{path: []interface{}{"obj", "nestedObjOne", "0"}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "badint"}},
},
{
name: "deepObject explode deeply nested object - array index not an int",
param: &openapi3.Parameter{
Name: "param", In: "query", Style: "deepObject", Explode: explode,
Schema: objectOf(
"obj", objectOf("nestedObjOne", integerArraySchema),
),
},
query: "param[obj][nestedObjOne][badindex]=badint",
err: &ParseError{path: []interface{}{"obj", "nestedObjOne"}, Kind: KindInvalidFormat, Reason: "could not convert value map to array: array indexes must be integers: strconv.Atoi: parsing \"badindex\": invalid syntax"},
},
{
name: "deepObject explode nested object with array",
param: &openapi3.Parameter{
Name: "param", In: "query", Style: "deepObject", Explode: explode,
Schema: objectOf(
"obj", objectOf("nestedObjOne", stringSchema, "nestedObjTwo", stringSchema),
"objTwo", stringArraySchema,
"objIgnored", objectOf("items", stringArraySchema),
),
},
query: "param[obj][nestedObjOne]=bar¶m[obj][nestedObjTwo]=foo¶m[objTwo][0]=f%26oo¶m[objTwo][1]=bar",
want: map[string]interface{}{
"obj": map[string]interface{}{"nestedObjOne": "bar", "nestedObjTwo": "foo"},
"objTwo": []interface{}{"f%26oo", "bar"},
},
found: true,
},
{
name: "deepObject explode nested object with array - bad value",
param: &openapi3.Parameter{
Name: "param", In: "query", Style: "deepObject", Explode: explode,
Schema: objectOf(
"obj", objectOf("nestedObjOne", stringSchema, "nestedObjTwo", booleanSchema),
"objTwo", stringArraySchema,
"objIgnored", objectOf("items", stringArraySchema),
),
},
query: "param[obj][nestedObjOne]=bar¶m[obj][nestedObjTwo]=bad¶m[objTwo][0]=f%26oo¶m[objTwo][1]=bar",
err: &ParseError{path: []interface{}{"obj", "nestedObjTwo"}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "bad"}},
},
{
name: "deepObject explode nested object with nested array",
param: &openapi3.Parameter{
Name: "param", In: "query", Style: "deepObject", Explode: explode,
Schema: objectOf(
"obj", objectOf("nestedObjOne", stringSchema, "nestedObjTwo", stringSchema),
"objTwo", objectOf("items", stringArraySchema),
"objIgnored", objectOf("items", stringArraySchema),
),
},
query: "param[obj][nestedObjOne]=bar¶m[obj][nestedObjTwo]=foo¶m[objTwo][items][0]=f%26oo¶m[objTwo][items][1]=bar",
want: map[string]interface{}{
"obj": map[string]interface{}{"nestedObjOne": "bar", "nestedObjTwo": "foo"},
"objTwo": map[string]interface{}{"items": []interface{}{"f%26oo", "bar"}},
},
found: true,
},
{
name: "deepObject explode nested object with nested array on different levels",
param: &openapi3.Parameter{
Name: "param", In: "query", Style: "deepObject", Explode: explode,
Schema: objectOf(
"obj", objectOf("nestedObjOne", objectOf("items", stringArraySchema)),
"objTwo", objectOf("items", stringArraySchema),
),
},
query: "param[obj][nestedObjOne][items][0]=baz¶m[objTwo][items][0]=foo¶m[objTwo][items][1]=bar",
want: map[string]interface{}{
"obj": map[string]interface{}{"nestedObjOne": map[string]interface{}{"items": []interface{}{"baz"}}},
"objTwo": map[string]interface{}{"items": []interface{}{"foo", "bar"}},
},
found: true,
},
{
name: "deepObject explode array of arrays",
param: &openapi3.Parameter{
Name: "param", In: "query", Style: "deepObject", Explode: explode,
Schema: objectOf(
"arr", arrayOf(arrayOf(integerSchema)),
),
},
query: "param[arr][1][1]=123¶m[arr][1][2]=456",
want: map[string]interface{}{
"arr": []interface{}{
nil,
[]interface{}{nil, int64(123), int64(456)},
},
},
found: true,
},
{
name: "deepObject explode nested array of objects - missing intermediate array index",
param: &openapi3.Parameter{
Name: "param", In: "query", Style: "deepObject", Explode: explode,
Schema: objectOf(
"arr", arrayOf(objectOf("key", booleanSchema)),
),
},
query: "param[arr][3][key]=true¶m[arr][0][key]=false",
want: map[string]interface{}{
"arr": []interface{}{
map[string]interface{}{"key": false},
nil,
nil,
map[string]interface{}{"key": true},
},
},
found: true,
},
{
name: "deepObject explode nested array of objects",
param: &openapi3.Parameter{
Name: "param", In: "query", Style: "deepObject", Explode: explode,
Schema: objectOf(
"arr", arrayOf(objectOf("key", booleanSchema)),
),
},
query: "param[arr][0][key]=true¶m[arr][1][key]=false",
found: true,
want: map[string]interface{}{
"arr": []interface{}{
map[string]interface{}{"key": true},
map[string]interface{}{"key": false},
},
},
},
{
name: "default",
param: &openapi3.Parameter{Name: "param", In: "query", Schema: objectSchema},
query: "id=foo&name=bar",
want: map[string]interface{}{"id": "foo", "name": "bar"},
found: true,
},
{
name: "invalid integer prop",
param: &openapi3.Parameter{Name: "param", In: "query", Schema: objectOf("foo", integerSchema)},
query: "foo=bar",
found: true,
err: &ParseError{path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "bar"}},
},
{
name: "invalid number prop",
param: &openapi3.Parameter{Name: "param", In: "query", Schema: objectOf("foo", numberSchema)},
query: "foo=bar",
found: true,
err: &ParseError{path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "bar"}},
},
{
name: "invalid boolean prop",
param: &openapi3.Parameter{Name: "param", In: "query", Schema: objectOf("foo", booleanSchema)},
query: "foo=bar",
found: true,
err: &ParseError{path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "bar"}},
},
},
},
{
name: "header primitive",
testCases: []testCase{
{
name: "simple",
param: &openapi3.Parameter{Name: "X-Param", In: "header", Style: "simple", Explode: noExplode, Schema: stringSchema},
header: "X-Param:foo",
want: "foo",
found: true,
},
{
name: "simple explode",
param: &openapi3.Parameter{Name: "X-Param", In: "header", Style: "simple", Explode: explode, Schema: stringSchema},
header: "X-Param:foo",
want: "foo",
found: true,
},
{
name: "default",
param: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: stringSchema},
header: "X-Param:foo",
want: "foo",
found: true,
},
{
name: "string",
param: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: stringSchema},
header: "X-Param:foo",
want: "foo",
found: true,
},
{
name: "integer",
param: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: integerSchema},
header: "X-Param:1",
want: int64(1),
found: true,
},
{
name: "integer invalid",
param: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: integerSchema},
header: "X-Param:foo",
found: true,
err: &ParseError{Kind: KindInvalidFormat, Value: "foo"},
},
{
name: "number",
param: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: numberSchema},
header: "X-Param:1.1",
want: 1.1,
found: true,
},
{
name: "number invalid",
param: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: numberSchema},
header: "X-Param:foo",
found: true,
err: &ParseError{Kind: KindInvalidFormat, Value: "foo"},
},
{
name: "boolean",
param: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: booleanSchema},
header: "X-Param:true",
want: true,
found: true,
},
{
name: "boolean invalid",
param: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: booleanSchema},
header: "X-Param:foo",
found: true,
err: &ParseError{Kind: KindInvalidFormat, Value: "foo"},
},
},
},
{
name: "header array",
testCases: []testCase{
{
name: "simple",
param: &openapi3.Parameter{Name: "X-Param", In: "header", Style: "simple", Explode: noExplode, Schema: stringArraySchema},
header: "X-Param:foo,bar",
want: []interface{}{"foo", "bar"},
found: true,
},
{
name: "simple explode",
param: &openapi3.Parameter{Name: "X-Param", In: "header", Style: "simple", Explode: explode, Schema: stringArraySchema},
header: "X-Param:foo,bar",
want: []interface{}{"foo", "bar"},
found: true,
},
{
name: "default",
param: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: stringArraySchema},
header: "X-Param:foo,bar",
want: []interface{}{"foo", "bar"},
found: true,
},
{
name: "invalid integer items",
param: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: arrayOf(integerSchema)},
header: "X-Param:1,foo",
found: true,
err: &ParseError{path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "foo"}},
},
{
name: "invalid number items",
param: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: arrayOf(numberSchema)},
header: "X-Param:1.1,foo",
found: true,
err: &ParseError{path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "foo"}},
},
{
name: "invalid boolean items",
param: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: arrayOf(booleanSchema)},
header: "X-Param:true,foo",
found: true,
err: &ParseError{path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "foo"}},
},
},
},
{
name: "header object",
testCases: []testCase{
{
name: "simple",
param: &openapi3.Parameter{Name: "X-Param", In: "header", Style: "simple", Explode: noExplode, Schema: objectSchema},
header: "X-Param:id,foo,name,bar",
want: map[string]interface{}{"id": "foo", "name": "bar"},
found: true,
},
{
name: "simple explode",
param: &openapi3.Parameter{Name: "X-Param", In: "header", Style: "simple", Explode: explode, Schema: objectSchema},
header: "X-Param:id=foo,name=bar",
want: map[string]interface{}{"id": "foo", "name": "bar"},
found: true,
},
{
name: "default",
param: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: objectSchema},
header: "X-Param:id,foo,name,bar",
want: map[string]interface{}{"id": "foo", "name": "bar"},
found: true,
},
{
name: "valid integer prop",
param: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: integerSchema},
header: "X-Param:88",
found: true,
want: int64(88),
},
{
name: "invalid integer prop",
param: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: objectOf("foo", integerSchema)},
header: "X-Param:foo,bar",
found: true,
err: &ParseError{path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "bar"}},
},
{
name: "invalid number prop",
param: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: objectOf("foo", numberSchema)},
header: "X-Param:foo,bar",
found: true,
err: &ParseError{path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "bar"}},
},
{
name: "invalid boolean prop",
param: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: objectOf("foo", booleanSchema)},
header: "X-Param:foo,bar",
found: true,
err: &ParseError{path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "bar"}},
},
},
},
{
name: "cookie primitive",
testCases: []testCase{
{
name: "form",
param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Style: "form", Explode: noExplode, Schema: stringSchema},
cookie: "X-Param:foo",
want: "foo",
found: true,
},
{
name: "form explode",
param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Style: "form", Explode: explode, Schema: stringSchema},
cookie: "X-Param:foo",
want: "foo",
found: true,
},
{
name: "default",
param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Schema: stringSchema},
cookie: "X-Param:foo",
want: "foo",
found: true,
},
{
name: "string",
param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Schema: stringSchema},
cookie: "X-Param:foo",
want: "foo",
found: true,
},
{
name: "integer",
param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Schema: integerSchema},
cookie: "X-Param:1",
want: int64(1),
found: true,
},
{
name: "integer invalid",
param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Schema: integerSchema},
cookie: "X-Param:foo",
found: true,
err: &ParseError{Kind: KindInvalidFormat, Value: "foo"},
},
{
name: "number",
param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Schema: numberSchema},
cookie: "X-Param:1.1",
want: 1.1,
found: true,
},
{
name: "number invalid",
param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Schema: numberSchema},
cookie: "X-Param:foo",
found: true,
err: &ParseError{Kind: KindInvalidFormat, Value: "foo"},
},
{
name: "boolean",
param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Schema: booleanSchema},
cookie: "X-Param:true",
want: true,
found: true,
},
{
name: "boolean invalid",
param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Schema: booleanSchema},
cookie: "X-Param:foo",
found: true,
err: &ParseError{Kind: KindInvalidFormat, Value: "foo"},
},
},
},
{
name: "cookie array",
testCases: []testCase{
{
name: "form",
param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Style: "form", Explode: noExplode, Schema: stringArraySchema},
cookie: "X-Param:foo,bar",
want: []interface{}{"foo", "bar"},
found: true,
},
{
name: "invalid integer items",
param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Style: "form", Explode: noExplode, Schema: arrayOf(integerSchema)},
cookie: "X-Param:1,foo",
found: true,
err: &ParseError{path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "foo"}},
},
{
name: "invalid number items",
param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Style: "form", Explode: noExplode, Schema: arrayOf(numberSchema)},
cookie: "X-Param:1.1,foo",
found: true,
err: &ParseError{path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "foo"}},
},
{
name: "invalid boolean items",
param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Style: "form", Explode: noExplode, Schema: arrayOf(booleanSchema)},
cookie: "X-Param:true,foo",
found: true,
err: &ParseError{path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "foo"}},
},
},
},
{
name: "cookie object",
testCases: []testCase{
{
name: "form",
param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Style: "form", Explode: noExplode, Schema: objectSchema},
cookie: "X-Param:id,foo,name,bar",
want: map[string]interface{}{"id": "foo", "name": "bar"},
found: true,
},
{
name: "invalid integer prop",
param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Style: "form", Explode: noExplode, Schema: objectOf("foo", integerSchema)},
cookie: "X-Param:foo,bar",
found: true,
err: &ParseError{path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "bar"}},
},
{
name: "invalid number prop",
param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Style: "form", Explode: noExplode, Schema: objectOf("foo", numberSchema)},
cookie: "X-Param:foo,bar",
found: true,
err: &ParseError{path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "bar"}},
},
{
name: "invalid boolean prop",
param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Style: "form", Explode: noExplode, Schema: objectOf("foo", booleanSchema)},
cookie: "X-Param:foo,bar",
found: true,
err: &ParseError{path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "bar"}},
},
},
},
}
for _, tg := range testGroups {
t.Run(tg.name, func(t *testing.T) {
for _, tc := range tg.testCases {
t.Run(tc.name, func(t *testing.T) {
req, err := http.NewRequest(http.MethodGet, "http://test.org/test"+tc.path, nil)
require.NoError(t, err, "failed to create a test request")
if tc.query != "" {
query := req.URL.Query()
for _, param := range strings.Split(tc.query, "&") {
v := strings.Split(param, "=")
query.Add(v[0], v[1])
}
req.URL.RawQuery = query.Encode()
}
if tc.header != "" {
v := strings.Split(tc.header, ":")
req.Header.Add(v[0], v[1])
}
if tc.cookie != "" {
v := strings.Split(tc.cookie, ":")
req.AddCookie(&http.Cookie{Name: v[0], Value: v[1]})
}
path := "/test"
if tc.path != "" {
path += "/{" + tc.param.Name + "}"
tc.param.Required = true
}
info := &openapi3.Info{
Title: "MyAPI",
Version: "0.1",
}
doc := &openapi3.T{OpenAPI: "3.0.0", Info: info, Paths: openapi3.NewPaths()}
op := &openapi3.Operation{
OperationID: "test",
Parameters: []*openapi3.ParameterRef{{Value: tc.param}},
Responses: openapi3.NewResponses(),
}
doc.AddOperation(path, http.MethodGet, op)
err = doc.Validate(context.Background())
require.NoError(t, err)
router, err := legacyrouter.NewRouter(doc)
require.NoError(t, err)
route, pathParams, err := router.FindRoute(req)
require.NoError(t, err)
input := &RequestValidationInput{Request: req, PathParams: pathParams, Route: route}
got, found, err := decodeStyledParameter(tc.param, input)
if tc.err != nil {
require.Error(t, err)
matchParseError(t, err, tc.err)
return
}
require.NoError(t, err)
require.EqualValues(t, tc.want, got)
require.Truef(t, found == tc.found, "got found: %t, want found: %t", found, tc.found)
})
}
})
}
}
func TestDecodeBody(t *testing.T) {
urlencodedForm := make(url.Values)
urlencodedForm.Set("a", "a1")
urlencodedForm.Set("b", "10")
urlencodedForm.Add("c", "c1")
urlencodedForm.Add("c", "c2")
urlencodedSpaceDelim := make(url.Values)
urlencodedSpaceDelim.Set("a", "a1")
urlencodedSpaceDelim.Set("b", "10")
urlencodedSpaceDelim.Add("c", "c1 c2")
urlencodedPipeDelim := make(url.Values)
urlencodedPipeDelim.Set("a", "a1")
urlencodedPipeDelim.Set("b", "10")
urlencodedPipeDelim.Add("c", "c1|c2")
d, err := json.Marshal(map[string]interface{}{"d1": "d1"})
require.NoError(t, err)
multipartForm, multipartFormMime, err := newTestMultipartForm([]*testFormPart{
{name: "a", contentType: "text/plain", data: strings.NewReader("a1")},
{name: "b", contentType: "application/json", data: strings.NewReader("10")},
{name: "c", contentType: "text/plain", data: strings.NewReader("c1")},
{name: "c", contentType: "text/plain", data: strings.NewReader("c2")},
{name: "d", contentType: "application/json", data: bytes.NewReader(d)},
{name: "f", contentType: "application/octet-stream", data: strings.NewReader("foo"), filename: "f1"},
{name: "g", data: strings.NewReader("g1")},
})
require.NoError(t, err)
multipartFormExtraPart, multipartFormMimeExtraPart, err := newTestMultipartForm([]*testFormPart{
{name: "a", contentType: "text/plain", data: strings.NewReader("a1")},
{name: "x", contentType: "text/plain", data: strings.NewReader("x1")},
})
require.NoError(t, err)
multipartAnyAdditionalProps, multipartMimeAnyAdditionalProps, err := newTestMultipartForm([]*testFormPart{
{name: "a", contentType: "text/plain", data: strings.NewReader("a1")},
{name: "x", contentType: "text/plain", data: strings.NewReader("x1")},
})
require.NoError(t, err)
multipartAdditionalProps, multipartMimeAdditionalProps, err := newTestMultipartForm([]*testFormPart{
{name: "a", contentType: "text/plain", data: strings.NewReader("a1")},
{name: "x", contentType: "text/plain", data: strings.NewReader("x1")},
})
require.NoError(t, err)
multipartAdditionalPropsErr, multipartMimeAdditionalPropsErr, err := newTestMultipartForm([]*testFormPart{
{name: "a", contentType: "text/plain", data: strings.NewReader("a1")},
{name: "x", contentType: "text/plain", data: strings.NewReader("x1")},
{name: "y", contentType: "text/plain", data: strings.NewReader("y1")},
})
require.NoError(t, err)
testCases := []struct {
name string
mime string
body io.Reader
schema *openapi3.Schema
encoding map[string]*openapi3.Encoding
want interface{}
wantErr error
}{
{
name: prefixUnsupportedCT,
mime: "application/xml",
wantErr: &ParseError{Kind: KindUnsupportedFormat},
},
{
name: "invalid body data",
mime: "application/json",
body: strings.NewReader("invalid"),
wantErr: &ParseError{Kind: KindInvalidFormat},
},
{
name: "plain text",
mime: "text/plain",
body: strings.NewReader("text"),
want: "text",
},
{
name: "json",
mime: "application/json",
body: strings.NewReader("\"foo\""),
want: "foo",
},
{
name: "x-yaml",
mime: "application/x-yaml",
body: strings.NewReader("foo"),
want: "foo",
},
{
name: "yaml",
mime: "application/yaml",
body: strings.NewReader("foo"),
want: "foo",
},
{
name: "urlencoded form",
mime: "application/x-www-form-urlencoded",
body: strings.NewReader(urlencodedForm.Encode()),
schema: openapi3.NewObjectSchema().
WithProperty("a", openapi3.NewStringSchema()).
WithProperty("b", openapi3.NewIntegerSchema()).
WithProperty("c", openapi3.NewArraySchema().WithItems(openapi3.NewStringSchema())),
want: map[string]interface{}{"a": "a1", "b": int64(10), "c": []interface{}{"c1", "c2"}},
},
{
name: "urlencoded space delimited",
mime: "application/x-www-form-urlencoded",
body: strings.NewReader(urlencodedSpaceDelim.Encode()),
schema: openapi3.NewObjectSchema().
WithProperty("a", openapi3.NewStringSchema()).
WithProperty("b", openapi3.NewIntegerSchema()).
WithProperty("c", openapi3.NewArraySchema().WithItems(openapi3.NewStringSchema())),
encoding: map[string]*openapi3.Encoding{
"c": {Style: openapi3.SerializationSpaceDelimited, Explode: openapi3.BoolPtr(false)},
},
want: map[string]interface{}{"a": "a1", "b": int64(10), "c": []interface{}{"c1", "c2"}},
},
{
name: "urlencoded pipe delimited",
mime: "application/x-www-form-urlencoded",
body: strings.NewReader(urlencodedPipeDelim.Encode()),
schema: openapi3.NewObjectSchema().
WithProperty("a", openapi3.NewStringSchema()).
WithProperty("b", openapi3.NewIntegerSchema()).
WithProperty("c", openapi3.NewArraySchema().WithItems(openapi3.NewStringSchema())),
encoding: map[string]*openapi3.Encoding{
"c": {Style: openapi3.SerializationPipeDelimited, Explode: openapi3.BoolPtr(false)},
},
want: map[string]interface{}{"a": "a1", "b": int64(10), "c": []interface{}{"c1", "c2"}},
},
{
name: "multipart",
mime: multipartFormMime,
body: multipartForm,
schema: openapi3.NewObjectSchema().
WithProperty("a", openapi3.NewStringSchema()).
WithProperty("b", openapi3.NewIntegerSchema()).
WithProperty("c", openapi3.NewArraySchema().WithItems(openapi3.NewStringSchema())).
WithProperty("d", openapi3.NewObjectSchema().WithProperty("d1", openapi3.NewStringSchema())).
WithProperty("f", openapi3.NewStringSchema().WithFormat("binary")).
WithProperty("g", openapi3.NewStringSchema()),
want: map[string]interface{}{"a": "a1", "b": json.Number("10"), "c": []interface{}{"c1", "c2"}, "d": map[string]interface{}{"d1": "d1"}, "f": "foo", "g": "g1"},
},
{
name: "multipartExtraPart",
mime: multipartFormMimeExtraPart,
body: multipartFormExtraPart,
schema: openapi3.NewObjectSchema().
WithProperty("a", openapi3.NewStringSchema()),
want: map[string]interface{}{"a": "a1"},
wantErr: &ParseError{Kind: KindOther},
},
{
name: "multipartAnyAdditionalProperties",
mime: multipartMimeAnyAdditionalProps,
body: multipartAnyAdditionalProps,
schema: openapi3.NewObjectSchema().
WithAnyAdditionalProperties().
WithProperty("a", openapi3.NewStringSchema()),
want: map[string]interface{}{"a": "a1"},
},
{
name: "multipartWithAdditionalProperties",
mime: multipartMimeAdditionalProps,
body: multipartAdditionalProps,
schema: openapi3.NewObjectSchema().
WithAdditionalProperties(openapi3.NewObjectSchema().
WithProperty("x", openapi3.NewStringSchema())).
WithProperty("a", openapi3.NewStringSchema()),
want: map[string]interface{}{"a": "a1", "x": "x1"},
},
{
name: "multipartWithAdditionalPropertiesError",
mime: multipartMimeAdditionalPropsErr,
body: multipartAdditionalPropsErr,
schema: openapi3.NewObjectSchema().
WithAdditionalProperties(openapi3.NewObjectSchema().
WithProperty("x", openapi3.NewStringSchema())).
WithProperty("a", openapi3.NewStringSchema()),
want: map[string]interface{}{"a": "a1", "x": "x1"},
wantErr: &ParseError{Kind: KindOther},
},
{
name: "file",
mime: "application/octet-stream",
body: strings.NewReader("foo"),
want: "foo",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
h := make(http.Header)
h.Set(headerCT, tc.mime)
var schemaRef *openapi3.SchemaRef
if tc.schema != nil {
schemaRef = tc.schema.NewRef()
}
encFn := func(name string) *openapi3.Encoding {
if tc.encoding == nil {
return nil
}
return tc.encoding[name]
}
_, got, err := decodeBody(tc.body, h, schemaRef, encFn)
if tc.wantErr != nil {
require.Error(t, err)
matchParseError(t, err, tc.wantErr)
return
}
require.NoError(t, err)
require.Truef(t, reflect.DeepEqual(got, tc.want), "got %v, want %v", got, tc.want)
})
}
}
type testFormPart struct {
name string
contentType string
data io.Reader
filename string
}
func newTestMultipartForm(parts []*testFormPart) (io.Reader, string, error) {
form := &bytes.Buffer{}
w := multipart.NewWriter(form)
defer w.Close()
for _, p := range parts {
var disp string
if p.filename == "" {
disp = fmt.Sprintf("form-data; name=%q", p.name)
} else {
disp = fmt.Sprintf("form-data; name=%q; filename=%q", p.name, p.filename)
}
h := make(textproto.MIMEHeader)
h.Set(headerCT, p.contentType)
h.Set("Content-Disposition", disp)
pw, err := w.CreatePart(h)
if err != nil {
return nil, "", err
}
if _, err = io.Copy(pw, p.data); err != nil {
return nil, "", err
}
}
return form, w.FormDataContentType(), nil
}
func TestRegisterAndUnregisterBodyDecoder(t *testing.T) {
var decoder BodyDecoder
decoder = func(body io.Reader, h http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (decoded interface{}, err error) {
var data []byte
if data, err = io.ReadAll(body); err != nil {
return
}
return strings.Split(string(data), ","), nil
}
contentType := "application/csv"
h := make(http.Header)
h.Set(headerCT, contentType)
originalDecoder := RegisteredBodyDecoder(contentType)
require.Nil(t, originalDecoder)
RegisterBodyDecoder(contentType, decoder)
require.Equal(t, fmt.Sprintf("%v", decoder), fmt.Sprintf("%v", RegisteredBodyDecoder(contentType)))
body := strings.NewReader("foo,bar")
schema := openapi3.NewArraySchema().WithItems(openapi3.NewStringSchema()).NewRef()
encFn := func(string) *openapi3.Encoding { return nil }
_, got, err := decodeBody(body, h, schema, encFn)
require.NoError(t, err)
require.Equal(t, []string{"foo", "bar"}, got)
UnregisterBodyDecoder(contentType)
originalDecoder = RegisteredBodyDecoder(contentType)
require.Nil(t, originalDecoder)
_, _, err = decodeBody(body, h, schema, encFn)
require.Equal(t, &ParseError{
Kind: KindUnsupportedFormat,
Reason: prefixUnsupportedCT + ` "application/csv"`,
}, err)
}
func matchParseError(t *testing.T, got, want error) {
t.Helper()
wErr, ok := want.(*ParseError)
if !ok {
t.Errorf("want error is not a ParseError")
return
}
gErr, ok := got.(*ParseError)
if !ok {
t.Errorf("got error is not a ParseError")
return
}
assert.Equalf(t, wErr.Kind, gErr.Kind, "ParseError Kind differs")
assert.Equalf(t, wErr.Value, gErr.Value, "ParseError Value differs")
assert.Equalf(t, wErr.Path(), gErr.Path(), "ParseError Path differs")
if wErr.Reason != "" {
assert.Equalf(t, wErr.Reason, gErr.Reason, "ParseError Reason differs")
}
if wErr.Cause != nil {
matchParseError(t, gErr.Cause, wErr.Cause)
}
}
kin-openapi-0.124.0/openapi3filter/req_resp_encoder.go 0000664 0000000 0000000 00000002770 14604223742 0022674 0 ustar 00root root 0000000 0000000 package openapi3filter
import (
"encoding/json"
"fmt"
"sync"
)
func encodeBody(body interface{}, mediaType string) ([]byte, error) {
if encoder := RegisteredBodyEncoder(mediaType); encoder != nil {
return encoder(body)
}
return nil, &ParseError{
Kind: KindUnsupportedFormat,
Reason: fmt.Sprintf("%s %q", prefixUnsupportedCT, mediaType),
}
}
// BodyEncoder really is an (encoding/json).Marshaler
type BodyEncoder func(body interface{}) ([]byte, error)
var bodyEncodersM sync.RWMutex
var bodyEncoders = map[string]BodyEncoder{
"application/json": json.Marshal,
}
// RegisterBodyEncoder enables package-wide decoding of contentType values
func RegisterBodyEncoder(contentType string, encoder BodyEncoder) {
if contentType == "" {
panic("contentType is empty")
}
if encoder == nil {
panic("encoder is not defined")
}
bodyEncodersM.Lock()
bodyEncoders[contentType] = encoder
bodyEncodersM.Unlock()
}
// UnregisterBodyEncoder disables package-wide decoding of contentType values
func UnregisterBodyEncoder(contentType string) {
if contentType == "" {
panic("contentType is empty")
}
bodyEncodersM.Lock()
delete(bodyEncoders, contentType)
bodyEncodersM.Unlock()
}
// RegisteredBodyEncoder returns the registered body encoder for the given content type.
//
// If no encoder was registered for the given content type, nil is returned.
func RegisteredBodyEncoder(contentType string) BodyEncoder {
bodyEncodersM.RLock()
mayBE := bodyEncoders[contentType]
bodyEncodersM.RUnlock()
return mayBE
}
kin-openapi-0.124.0/openapi3filter/req_resp_encoder_test.go 0000664 0000000 0000000 00000002054 14604223742 0023726 0 ustar 00root root 0000000 0000000 package openapi3filter
import (
"fmt"
"net/http"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
func TestRegisterAndUnregisterBodyEncoder(t *testing.T) {
var encoder BodyEncoder
encoder = func(body interface{}) (data []byte, err error) {
return []byte(strings.Join(body.([]string), ",")), nil
}
contentType := "text/csv"
h := make(http.Header)
h.Set(headerCT, contentType)
originalEncoder := RegisteredBodyEncoder(contentType)
require.Nil(t, originalEncoder)
RegisterBodyEncoder(contentType, encoder)
require.Equal(t, fmt.Sprintf("%v", encoder), fmt.Sprintf("%v", RegisteredBodyEncoder(contentType)))
body := []string{"foo", "bar"}
got, err := encodeBody(body, contentType)
require.NoError(t, err)
require.Equal(t, []byte("foo,bar"), got)
UnregisterBodyEncoder(contentType)
originalEncoder = RegisteredBodyEncoder(contentType)
require.Nil(t, originalEncoder)
_, err = encodeBody(body, contentType)
require.Equal(t, &ParseError{
Kind: KindUnsupportedFormat,
Reason: prefixUnsupportedCT + ` "text/csv"`,
}, err)
}
kin-openapi-0.124.0/openapi3filter/testdata/ 0000775 0000000 0000000 00000000000 14604223742 0020631 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3filter/testdata/fixtures/ 0000775 0000000 0000000 00000000000 14604223742 0022502 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3filter/testdata/fixtures/petstore.json 0000664 0000000 0000000 00000114454 14604223742 0025253 0 ustar 00root root 0000000 0000000 {
"openapi": "3.0.0",
"servers": [
{
"url": "https://petstore.swagger.io/v2"
},
{
"url": "http://petstore.swagger.io/v2"
}
],
"info": {
"description": "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.",
"version": "1.0.0",
"title": "Swagger Petstore",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"email": "apiteam@swagger.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
}
},
"tags": [
{
"name": "pet",
"description": "Everything about your Pets",
"externalDocs": {
"description": "Find out more",
"url": "http://swagger.io"
}
},
{
"name": "store",
"description": "Access to Petstore orders"
},
{
"name": "user",
"description": "Operations about user",
"externalDocs": {
"description": "Find out more about our store",
"url": "http://swagger.io"
}
}
],
"paths": {
"/pet": {
"post": {
"tags": [
"pet"
],
"summary": "Add a new pet to the store",
"description": "",
"operationId": "addPet",
"responses": {
"405": {
"description": "Invalid input"
}
},
"security": [
{
"petstore_auth": [
"write:pets",
"read:pets"
]
}
],
"requestBody": {
"$ref": "#/components/requestBodies/PetWithRequired"
},
"parameters": [
{
"schema": {
"type": "string",
"enum": [
"demo",
"prod"
]
},
"in": "header",
"name": "x-environment",
"description": "Where to send the data for processing"
}
]
},
"patch": {
"tags": [
"pet"
],
"summary": "Update an existing pet",
"description": "",
"operationId": "updatePet",
"responses": {
"400": {
"description": "Invalid ID supplied"
},
"404": {
"description": "Pet not found"
},
"405": {
"description": "Validation exception"
}
},
"security": [
{
"petstore_auth": [
"write:pets",
"read:pets"
]
}
],
"requestBody": {
"$ref": "#/components/requestBodies/Pet"
}
}
},
"/pet2": {
"post": {
"tags": [
"pet"
],
"summary": "Add a new pet to the store",
"description": "",
"operationId": "addPet2",
"responses": {
"405": {
"description": "Invalid input"
}
},
"security": [
{
"petstore_auth": [
"write:pets",
"read:pets"
]
}
],
"requestBody": {
"$ref": "#/components/requestBodies/PetAllOfRequiredProperties"
}
}
},
"/pet/filter": {
"get": {
"tags": [
"pet"
],
"summary": "Finds Pets by status",
"operationId": "filterPets",
"parameters": [
{
"in": "query",
"name": "deepFilter",
"style": "deepObject",
"explode": true,
"allowReserved": true,
"schema": {
"type": "object",
"properties": {
"strings": {
"type": "array",
"items": {
"type": "string"
}
},
"booleans": {
"type": "array",
"items": {
"type": "boolean"
}
},
"numbers": {
"type": "array",
"items": {
"type": "number"
}
},
"integers": {
"type": "array",
"items": {
"type": "integer"
}
}
}
}
}
],
"responses": {
"200": {
"description": "successful operation",
"content": {
"application/xml": {
"schema": {
"type": "array",
"items": {
"allOf": [
{
"$ref": "#/components/schemas/Pet"
},
{
"$ref": "#/components/schemas/PetRequiredProperties"
}
]
}
}
},
"application/json": {
"schema": {
"type": "array",
"items": {
"allOf": [
{
"$ref": "#/components/schemas/Pet"
},
{
"$ref": "#/components/schemas/PetRequiredProperties"
}
]
}
}
}
}
}
},
"security": [
{
"petstore_auth": [
"write:pets",
"read:pets"
]
}
]
}
},
"/pet/findByStatus": {
"get": {
"tags": [
"pet"
],
"summary": "Finds Pets by status",
"description": "Multiple status values can be provided with comma separated strings",
"operationId": "findPetsByStatus",
"parameters": [
{
"name": "status",
"in": "query",
"description": "Status values that need to be considered for filter",
"required": true,
"explode": true,
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"available",
"pending",
"sold"
],
"default": "available"
}
}
}
],
"responses": {
"200": {
"description": "successful operation",
"content": {
"application/xml": {
"schema": {
"type": "array",
"items": {
"allOf": [
{
"$ref": "#/components/schemas/Pet"
},
{
"$ref": "#/components/schemas/PetRequiredProperties"
}
]
}
}
},
"application/json": {
"schema": {
"type": "array",
"items": {
"allOf": [
{
"$ref": "#/components/schemas/Pet"
},
{
"$ref": "#/components/schemas/PetRequiredProperties"
}
]
}
}
}
}
},
"400": {
"description": "Invalid status value"
}
},
"security": [
{
"petstore_auth": [
"write:pets",
"read:pets"
]
}
]
}
},
"/pets/": {
"get": {
"tags": [
"pet"
],
"summary": "Find pets by the specified filters",
"description": "Returns a list of pets that comply with the specified filters",
"operationId": "findPets",
"parameters": [
{
"name": "status",
"in": "query",
"description": "Status values that need to be considered for filter",
"required": false,
"explode": true,
"allowEmptyValue": true,
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"available",
"pending",
"sold"
],
"default": "available"
}
}
},
{
"name": "tags",
"in": "query",
"description": "Tags to filter by",
"required": false,
"explode": true,
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
},
{
"name": "kind",
"in": "query",
"description": "Kinds to filter by",
"required": false,
"explode": false,
"style": "pipeDelimited",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"dog",
"cat",
"turtle",
"bird,with,commas"
]
}
}
}
],
"responses": {
"200": {
"description": "successful operation",
"content": {
"application/xml": {
"schema": {
"type": "array",
"items": {
"allOf": [
{
"$ref": "#/components/schemas/Pet"
},
{
"$ref": "#/components/schemas/PetRequiredProperties"
}
]
}
}
},
"application/json": {
"schema": {
"type": "array",
"items": {
"allOf": [
{
"$ref": "#/components/schemas/Pet"
},
{
"$ref": "#/components/schemas/PetRequiredProperties"
}
]
}
}
}
}
},
"400": {
"description": "Invalid status value"
}
},
"security": [
{
"petstore_auth": [
"write:pets",
"read:pets"
]
}
]
}
},
"/pet/findByTags": {
"get": {
"tags": [
"pet"
],
"summary": "Finds Pets by tags",
"description": "Muliple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.",
"operationId": "findPetsByTags",
"parameters": [
{
"name": "tags",
"in": "query",
"description": "Tags to filter by",
"required": true,
"explode": true,
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
}
],
"responses": {
"200": {
"description": "successful operation",
"content": {
"application/xml": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Pet"
}
}
},
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Pet"
}
}
}
}
},
"400": {
"description": "Invalid tag value"
}
},
"security": [
{
"petstore_auth": [
"write:pets",
"read:pets"
]
}
],
"deprecated": true
}
},
"/pet/findByIds": {
"get": {
"tags": [
"pet"
],
"summary": "Finds Pets by IDs",
"description": "Muliple IDs can be provided with comma separated strings. Use id1, id2, id3 for testing.",
"operationId": "findPetsByIds",
"parameters": [
{
"name": "ids",
"in": "query",
"description": "IDs to filter by",
"required": true,
"explode": false,
"schema": {
"type": "array",
"items": {
"type": "integer",
"format": "int64"
}
}
}
],
"responses": {
"200": {
"description": "successful operation",
"content": {
"application/xml": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Pet"
}
}
},
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Pet"
}
}
}
}
},
"400": {
"description": "Invalid ID value"
}
},
"security": [
{
"petstore_auth": [
"write:pets",
"read:pets"
]
}
],
"deprecated": true
}
},
"/pet/findByKind": {
"get": {
"tags": [
"pet"
],
"summary": "Finds Pets by Kind",
"description": "Muliple kinds can be provided with comma separated strings. Use id1, id2, id3 for testing.",
"operationId": "findPetsByKind",
"parameters": [
{
"name": "kind",
"in": "query",
"description": "Kinds to filter by",
"required": true,
"explode": false,
"style": "pipeDelimited",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"dog",
"cat",
"turtle",
"bird,with,commas"
]
}
}
}
],
"responses": {
"200": {
"description": "successful operation",
"content": {
"application/xml": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Pet"
}
}
},
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Pet"
}
}
}
}
},
"400": {
"description": "Invalid ID value"
}
},
"security": [
{
"petstore_auth": [
"write:pets",
"read:pets"
]
}
],
"deprecated": true
}
},
"/pet/{petId}": {
"get": {
"tags": [
"pet"
],
"summary": "Find pet by ID",
"description": "Returns a single pet",
"operationId": "getPetById",
"parameters": [
{
"name": "petId",
"in": "path",
"description": "ID of pet to return",
"required": true,
"schema": {
"type": "integer",
"format": "int64"
}
}
],
"responses": {
"200": {
"description": "successful operation",
"content": {
"application/xml": {
"schema": {
"$ref": "#/components/schemas/Pet"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/Pet"
}
}
}
},
"400": {
"description": "Invalid ID supplied"
},
"404": {
"description": "Pet not found"
}
},
"security": [
{
"api_key": []
}
]
},
"post": {
"tags": [
"pet"
],
"summary": "Updates a pet in the store with form data",
"description": "",
"operationId": "updatePetWithForm",
"parameters": [
{
"name": "petId",
"in": "path",
"description": "ID of pet that needs to be updated",
"required": true,
"schema": {
"type": "integer",
"format": "int64"
}
}
],
"responses": {
"405": {
"description": "Invalid input"
}
},
"security": [
{
"petstore_auth": [
"write:pets",
"read:pets"
]
}
],
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"type": "object",
"properties": {
"name": {
"description": "Updated name of the pet",
"type": "string"
},
"status": {
"description": "Updated status of the pet",
"type": "string"
}
}
}
}
}
}
},
"delete": {
"tags": [
"pet"
],
"summary": "Deletes a pet",
"description": "",
"operationId": "deletePet",
"parameters": [
{
"name": "api_key",
"in": "header",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "petId",
"in": "path",
"description": "Pet id to delete",
"required": true,
"schema": {
"type": "integer",
"format": "int64"
}
}
],
"responses": {
"400": {
"description": "Invalid ID supplied"
},
"404": {
"description": "Pet not found"
}
},
"security": [
{
"petstore_auth": [
"write:pets",
"read:pets"
]
}
]
}
},
"/pet/{petId}/uploadImage": {
"post": {
"tags": [
"pet"
],
"summary": "uploads an image",
"description": "",
"operationId": "uploadFile",
"parameters": [
{
"name": "petId",
"in": "path",
"description": "ID of pet to update",
"required": true,
"schema": {
"type": "integer",
"format": "int64"
}
}
],
"responses": {
"200": {
"description": "successful operation",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponse"
}
}
}
}
},
"security": [
{
"petstore_auth": [
"write:pets",
"read:pets"
]
}
],
"requestBody": {
"content": {
"application/octet-stream": {
"schema": {
"type": "string",
"format": "binary"
}
}
}
}
}
},
"/store/inventory": {
"get": {
"tags": [
"store"
],
"summary": "Returns pet inventories by status",
"description": "Returns a map of status codes to quantities",
"operationId": "getInventory",
"responses": {
"200": {
"description": "successful operation",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": {
"type": "integer",
"format": "int32"
}
}
}
}
}
},
"security": [
{
"api_key": []
}
]
}
},
"/store/order": {
"post": {
"tags": [
"store"
],
"summary": "Place an order for a pet",
"description": "",
"operationId": "placeOrder",
"responses": {
"200": {
"description": "successful operation",
"content": {
"application/xml": {
"schema": {
"$ref": "#/components/schemas/Order"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/Order"
}
}
}
},
"400": {
"description": "Invalid Order"
}
},
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Order"
}
}
},
"description": "order placed for purchasing the pet",
"required": true
}
}
},
"/store/order/{orderId}": {
"get": {
"tags": [
"store"
],
"summary": "Find purchase order by ID",
"description": "For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions",
"operationId": "getOrderById",
"parameters": [
{
"name": "orderId",
"in": "path",
"description": "ID of pet that needs to be fetched",
"required": true,
"schema": {
"type": "integer",
"format": "int64",
"minimum": 1,
"maximum": 10
}
}
],
"responses": {
"200": {
"description": "successful operation",
"content": {
"application/xml": {
"schema": {
"$ref": "#/components/schemas/Order"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/Order"
}
}
}
},
"400": {
"description": "Invalid ID supplied"
},
"404": {
"description": "Order not found"
}
}
},
"delete": {
"tags": [
"store"
],
"summary": "Delete purchase order by ID",
"description": "For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors",
"operationId": "deleteOrder",
"parameters": [
{
"name": "orderId",
"in": "path",
"description": "ID of the order that needs to be deleted",
"required": true,
"schema": {
"type": "integer",
"format": "int64",
"minimum": 1
}
}
],
"responses": {
"400": {
"description": "Invalid ID supplied"
},
"404": {
"description": "Order not found"
}
}
}
},
"/user": {
"post": {
"tags": [
"user"
],
"summary": "Create user",
"description": "This can only be done by the logged in user.",
"operationId": "createUser",
"responses": {
"default": {
"description": "successful operation"
}
},
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/User"
}
}
},
"description": "Created user object",
"required": true
}
}
},
"/user/createWithArray": {
"post": {
"tags": [
"user"
],
"summary": "Creates list of users with given input array",
"description": "",
"operationId": "createUsersWithArrayInput",
"responses": {
"default": {
"description": "successful operation"
}
},
"requestBody": {
"$ref": "#/components/requestBodies/UserArray"
}
}
},
"/user/createWithList": {
"post": {
"tags": [
"user"
],
"summary": "Creates list of users with given input array",
"description": "",
"operationId": "createUsersWithListInput",
"responses": {
"default": {
"description": "successful operation"
}
},
"requestBody": {
"$ref": "#/components/requestBodies/UserArray"
}
}
},
"/user/login": {
"get": {
"tags": [
"user"
],
"summary": "Logs user into the system",
"description": "",
"operationId": "loginUser",
"parameters": [
{
"name": "username",
"in": "query",
"description": "The user name for login",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "password",
"in": "query",
"description": "The password for login in clear text",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "successful operation",
"headers": {
"X-Rate-Limit": {
"description": "calls per hour allowed by the user",
"schema": {
"type": "integer",
"format": "int32"
}
},
"X-Expires-After": {
"description": "date in UTC when token expires",
"schema": {
"type": "string",
"format": "date-time"
}
}
},
"content": {
"application/xml": {
"schema": {
"type": "string"
}
},
"application/json": {
"schema": {
"type": "string"
}
}
}
},
"400": {
"description": "Invalid username/password supplied"
}
}
}
},
"/user/logout": {
"get": {
"tags": [
"user"
],
"summary": "Logs out current logged in user session",
"description": "",
"operationId": "logoutUser",
"responses": {
"default": {
"description": "successful operation"
}
}
}
},
"/user/{username}": {
"get": {
"tags": [
"user"
],
"summary": "Get user by user name",
"description": "",
"operationId": "getUserByName",
"parameters": [
{
"name": "username",
"in": "path",
"description": "The name that needs to be fetched. Use user1 for testing. ",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "successful operation",
"content": {
"application/xml": {
"schema": {
"$ref": "#/components/schemas/User"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/User"
}
}
}
},
"400": {
"description": "Invalid username supplied"
},
"404": {
"description": "User not found"
}
}
},
"put": {
"tags": [
"user"
],
"summary": "Updated user",
"description": "This can only be done by the logged in user.",
"operationId": "updateUser",
"parameters": [
{
"name": "username",
"in": "path",
"description": "name that need to be updated",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"400": {
"description": "Invalid user supplied"
},
"404": {
"description": "User not found"
}
},
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/User"
}
}
},
"description": "Updated user object",
"required": true
}
},
"delete": {
"tags": [
"user"
],
"summary": "Delete user",
"description": "This can only be done by the logged in user.",
"operationId": "deleteUser",
"parameters": [
{
"name": "username",
"in": "path",
"description": "The name that needs to be deleted",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"400": {
"description": "Invalid username supplied"
},
"404": {
"description": "User not found"
}
}
}
}
},
"externalDocs": {
"description": "Find out more about Swagger",
"url": "http://swagger.io"
},
"components": {
"schemas": {
"Order": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"petId": {
"type": "integer",
"format": "int64"
},
"quantity": {
"type": "integer",
"format": "int32"
},
"shipDate": {
"type": "string",
"format": "date-time"
},
"status": {
"type": "string",
"description": "Order Status",
"enum": [
"placed",
"approved",
"delivered"
]
},
"complete": {
"type": "boolean",
"default": false
}
},
"xml": {
"name": "Order"
}
},
"User": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"username": {
"type": "string"
},
"firstName": {
"type": "string"
},
"lastName": {
"type": "string"
},
"email": {
"type": "string"
},
"password": {
"type": "string"
},
"phone": {
"type": "string"
},
"userStatus": {
"type": "integer",
"format": "int32",
"description": "User Status"
}
},
"xml": {
"name": "User"
}
},
"Category": {
"type": "object",
"required": [
"name"
],
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"name": {
"type": "string"
},
"tags": {
"type": "array",
"xml": {
"name": "tag",
"wrapped": true
},
"items": {
"$ref": "#/components/schemas/Tag"
}
}
},
"xml": {
"name": "Category"
}
},
"Tag": {
"type": "object",
"required": [
"name"
],
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"name": {
"type": "string"
}
},
"xml": {
"name": "Tag"
}
},
"ApiResponse": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"type": {
"type": "string"
},
"message": {
"type": "string"
}
}
},
"Pet": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64",
"readOnly": true
},
"category": {
"$ref": "#/components/schemas/Category"
},
"name": {
"type": "string",
"example": "doggie"
},
"photoUrls": {
"type": "array",
"xml": {
"name": "photoUrl",
"wrapped": true
},
"items": {
"type": "string"
}
},
"tags": {
"type": "array",
"xml": {
"name": "tag",
"wrapped": true
},
"items": {
"$ref": "#/components/schemas/Tag"
}
},
"status": {
"type": "string",
"description": "pet status in the store",
"enum": [
"available",
"pending",
"sold"
]
}
},
"xml": {
"name": "Pet"
}
},
"PetRequiredProperties": {
"type": "object",
"required": [
"name",
"photoUrls"
]
},
"PetWithRequired": {
"type": "object",
"required": [
"name",
"photoUrls"
],
"properties": {
"id": {
"type": "integer",
"format": "int64",
"readOnly": true
},
"category": {
"$ref": "#/components/schemas/Category"
},
"name": {
"type": "string",
"example": "doggie"
},
"photoUrls": {
"type": "array",
"xml": {
"name": "photoUrl",
"wrapped": true
},
"items": {
"type": "string"
}
},
"tags": {
"type": "array",
"xml": {
"name": "tag",
"wrapped": true
},
"items": {
"$ref": "#/components/schemas/Tag"
}
},
"status": {
"type": "string",
"description": "pet status in the store",
"enum": [
"available",
"pending",
"sold"
]
}
},
"xml": {
"name": "Pet"
}
}
},
"requestBodies": {
"PetAllOfRequiredProperties": {
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/Pet"
},
{
"$ref": "#/components/schemas/PetRequiredProperties"
}
]
}
},
"application/xml": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/Pet"
},
{
"$ref": "#/components/schemas/PetRequiredProperties"
}
]
}
}
},
"required": true
},
"Pet": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Pet"
}
},
"application/xml": {
"schema": {
"$ref": "#/components/schemas/Pet"
}
}
},
"description": "Pet object that needs to be added to the store",
"required": true
},
"PetWithRequired": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PetWithRequired"
}
},
"application/xml": {
"schema": {
"$ref": "#/components/schemas/PetWithRequired"
}
}
},
"description": "Pet object that needs to be added to the store",
"required": true
},
"UserArray": {
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/User"
}
}
}
},
"description": "List of user object",
"required": true
}
},
"securitySchemes": {
"petstore_auth": {
"type": "oauth2",
"flows": {
"implicit": {
"authorizationUrl": "https://petstore.swagger.io/oauth/dialog",
"scopes": {
"write:pets": "modify pets in your account",
"read:pets": "read your pets"
}
}
}
},
"api_key": {
"type": "apiKey",
"name": "api_key",
"in": "header"
}
}
}
}
kin-openapi-0.124.0/openapi3filter/testdata/petstore.yaml 0000664 0000000 0000000 00000005054 14604223742 0023366 0 ustar 00root root 0000000 0000000 openapi: "3.0.0"
info:
description: "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters."
version: "1.0.0"
title: "Swagger Petstore"
termsOfService: "http://swagger.io/terms/"
contact:
email: "apiteam@swagger.io"
license:
name: "Apache 2.0"
url: "http://www.apache.org/licenses/LICENSE-2.0.html"
tags:
- name: "pet"
description: "Everything about your Pets"
externalDocs:
description: "Find out more"
url: "http://swagger.io"
- name: "store"
description: "Access to Petstore orders"
- name: "user"
description: "Operations about user"
externalDocs:
description: "Find out more about our store"
url: "http://swagger.io"
paths:
/pet:
post:
tags:
- "pet"
summary: "Add a new pet to the store"
description: ""
operationId: "addPet"
parameters:
- name: num
in: query
schema:
type: integer
minimum: 1
requestBody:
required: true
content:
'application/json':
schema:
$ref: '#/components/schemas/Pet'
responses:
"405":
description: "Invalid input"
components:
schemas:
Category:
type: "object"
properties:
id:
type: "integer"
format: "int64"
name:
type: "string"
xml:
name: "Category"
Tag:
type: "object"
properties:
id:
type: "integer"
format: "int64"
name:
type: "string"
xml:
name: "Tag"
Pet:
type: "object"
required:
- "name"
- "photoUrls"
properties:
id:
type: "integer"
format: "int64"
category:
$ref: "#/components/schemas/Category"
name:
type: "string"
example: "doggie"
photoUrls:
type: "array"
xml:
name: "photoUrl"
wrapped: true
items:
type: "string"
tags:
type: "array"
xml:
name: "tag"
wrapped: true
items:
$ref: "#/components/schemas/Tag"
status:
type: "string"
description: "pet status in the store"
enum:
- "available"
- "pending"
- "sold"
xml:
name: "Pet"
kin-openapi-0.124.0/openapi3filter/unpack_errors_test.go 0000664 0000000 0000000 00000010005 14604223742 0023257 0 ustar 00root root 0000000 0000000 package openapi3filter_test
import (
"fmt"
"net/http"
"net/http/httptest"
"sort"
"strings"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/openapi3filter"
"github.com/getkin/kin-openapi/routers/gorillamux"
)
func Example() {
loader := openapi3.NewLoader()
doc, err := loader.LoadFromFile("./testdata/petstore.yaml")
if err != nil {
panic(err)
}
if err = doc.Validate(loader.Context); err != nil {
panic(err)
}
router, err := gorillamux.NewRouter(doc)
if err != nil {
panic(err)
}
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
route, pathParams, err := router.FindRoute(r)
if err != nil {
fmt.Println(err.Error())
w.WriteHeader(http.StatusInternalServerError)
return
}
err = openapi3filter.ValidateRequest(r.Context(), &openapi3filter.RequestValidationInput{
Request: r,
PathParams: pathParams,
Route: route,
Options: &openapi3filter.Options{
MultiError: true,
},
})
switch err := err.(type) {
case nil:
case openapi3.MultiError:
issues := convertError(err)
names := make([]string, 0, len(issues))
for k := range issues {
names = append(names, k)
}
sort.Strings(names)
for _, k := range names {
msgs := issues[k]
fmt.Println("===== Start New Error =====")
fmt.Println(k + ":")
for _, msg := range msgs {
fmt.Printf("\t%s\n", msg)
}
}
w.WriteHeader(http.StatusBadRequest)
default:
fmt.Println(err.Error())
w.WriteHeader(http.StatusBadRequest)
}
}))
defer ts.Close()
// (note invalid type for name and invalid status)
body := strings.NewReader(`{"name": 100, "photoUrls": [], "status": "invalidStatus"}`)
req, err := http.NewRequest("POST", ts.URL+"/pet?num=0", body)
if err != nil {
panic(err)
}
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
fmt.Printf("response: %d %s\n", resp.StatusCode, resp.Body)
// Output:
// ===== Start New Error =====
// @body.name:
// Error at "/name": value must be a string
// Schema:
// {
// "example": "doggie",
// "type": "string"
// }
//
// Value:
// 100
//
// ===== Start New Error =====
// @body.status:
// Error at "/status": value is not one of the allowed values ["available","pending","sold"]
// Schema:
// {
// "description": "pet status in the store",
// "enum": [
// "available",
// "pending",
// "sold"
// ],
// "type": "string"
// }
//
// Value:
// "invalidStatus"
//
// ===== Start New Error =====
// query.num:
// parameter "num" in query has an error: number must be at least 1
// Schema:
// {
// "minimum": 1,
// "type": "integer"
// }
//
// Value:
// 0
//
// response: 400 {}
}
func convertError(me openapi3.MultiError) map[string][]string {
issues := make(map[string][]string)
for _, err := range me {
const prefixBody = "@body"
switch err := err.(type) {
case *openapi3.SchemaError:
// Can inspect schema validation errors here, e.g. err.Value
field := prefixBody
if path := err.JSONPointer(); len(path) > 0 {
field = fmt.Sprintf("%s.%s", field, strings.Join(path, "."))
}
issues[field] = append(issues[field], err.Error())
case *openapi3filter.RequestError: // possible there were multiple issues that failed validation
// check if invalid HTTP parameter
if err.Parameter != nil {
prefix := err.Parameter.In
name := fmt.Sprintf("%s.%s", prefix, err.Parameter.Name)
issues[name] = append(issues[name], err.Error())
continue
}
if err, ok := err.Err.(openapi3.MultiError); ok {
for k, v := range convertError(err) {
issues[k] = append(issues[k], v...)
}
continue
}
// check if requestBody
if err.RequestBody != nil {
issues[prefixBody] = append(issues[prefixBody], err.Error())
continue
}
default:
const unknown = "@unknown"
issues[unknown] = append(issues[unknown], err.Error())
}
}
return issues
}
kin-openapi-0.124.0/openapi3filter/validate_readonly_test.go 0000664 0000000 0000000 00000012413 14604223742 0024075 0 ustar 00root root 0000000 0000000 package openapi3filter
import (
"bytes"
"io"
"net/http"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
legacyrouter "github.com/getkin/kin-openapi/routers/legacy"
)
func TestReadOnlyWriteOnlyPropertiesValidation(t *testing.T) {
type testCase struct {
name string
requestSchema string
responseSchema string
requestBody string
responseBody string
responseErrContains string
requestErrContains string
}
testCases := []testCase{
{
name: "valid_readonly_in_response_and_valid_writeonly_in_request",
requestSchema: `
"schema":{
"type": "object",
"required": ["_id"],
"properties": {
"_id": {
"type": "string",
"writeOnly": true
}
}
}`,
responseSchema: `
"schema":{
"type": "object",
"required": ["access_token"],
"properties": {
"access_token": {
"type": "string",
"readOnly": true
}
}
}`,
requestBody: `{"_id": "bt6kdc3d0cvp6u8u3ft0"}`,
responseBody: `{"access_token": "abcd"}`,
},
{
name: "valid_readonly_in_response_and_invalid_readonly_in_request",
requestSchema: `
"schema":{
"type": "object",
"required": ["_id"],
"properties": {
"_id": {
"type": "string",
"readOnly": true
}
}
}`,
responseSchema: `
"schema":{
"type": "object",
"required": ["access_token"],
"properties": {
"access_token": {
"type": "string",
"readOnly": true
}
}
}`,
requestBody: `{"_id": "bt6kdc3d0cvp6u8u3ft0"}`,
responseBody: `{"access_token": "abcd"}`,
requestErrContains: `readOnly property "_id" in request`,
},
{
name: "invalid_writeonly_in_response_and_valid_writeonly_in_request",
requestSchema: `
"schema":{
"type": "object",
"required": ["_id"],
"properties": {
"_id": {
"type": "string",
"writeOnly": true
}
}
}`,
responseSchema: `
"schema":{
"type": "object",
"required": ["access_token"],
"properties": {
"access_token": {
"type": "string",
"writeOnly": true
}
}
}`,
requestBody: `{"_id": "bt6kdc3d0cvp6u8u3ft0"}`,
responseBody: `{"access_token": "abcd"}`,
responseErrContains: `writeOnly property "access_token" in response`,
},
{
name: "invalid_writeonly_in_response_and_invalid_readonly_in_request",
requestSchema: `
"schema":{
"type": "object",
"required": ["_id"],
"properties": {
"_id": {
"type": "string",
"readOnly": true
}
}
}`,
responseSchema: `
"schema":{
"type": "object",
"required": ["access_token"],
"properties": {
"access_token": {
"type": "string",
"writeOnly": true
}
}
}`,
requestBody: `{"_id": "bt6kdc3d0cvp6u8u3ft0"}`,
responseBody: `{"access_token": "abcd"}`,
responseErrContains: `writeOnly property "access_token" in response`,
requestErrContains: `readOnly property "_id" in request`,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
spec := bytes.NewBufferString(`{
"openapi": "3.0.3",
"info": {
"version": "1.0.0",
"title": "title"
},
"paths": {
"/accounts": {
"post": {
"description": "Create a new account",
"requestBody": {
"required": true,
"content": {
"application/json": {`)
spec.WriteString(tc.requestSchema)
spec.WriteString(`}
}
},
"responses": {
"201": {
"description": "Successfully created a new account",
"content": {
"application/json": {`)
spec.WriteString(tc.responseSchema)
spec.WriteString(`}
}
},
"400": {
"description": "The server could not understand the request due to invalid syntax",
}
}
}
}
}
}`)
sl := openapi3.NewLoader()
doc, err := sl.LoadFromData(spec.Bytes())
require.NoError(t, err)
err = doc.Validate(sl.Context)
require.NoError(t, err)
router, err := legacyrouter.NewRouter(doc)
require.NoError(t, err)
httpReq, err := http.NewRequest(http.MethodPost, "/accounts", strings.NewReader(tc.requestBody))
require.NoError(t, err)
httpReq.Header.Add(headerCT, "application/json")
route, pathParams, err := router.FindRoute(httpReq)
require.NoError(t, err)
reqValidationInput := &RequestValidationInput{
Request: httpReq,
PathParams: pathParams,
Route: route,
}
if tc.requestSchema != "" {
err = ValidateRequest(sl.Context, reqValidationInput)
if tc.requestErrContains != "" {
require.Error(t, err)
require.ErrorContains(t, err, tc.requestErrContains)
} else {
require.NoError(t, err)
}
}
if tc.responseSchema != "" {
err = ValidateResponse(sl.Context, &ResponseValidationInput{
RequestValidationInput: reqValidationInput,
Status: 201,
Header: httpReq.Header,
Body: io.NopCloser(strings.NewReader(tc.responseBody)),
})
if tc.responseErrContains != "" {
require.Error(t, err)
require.ErrorContains(t, err, tc.responseErrContains)
} else {
require.NoError(t, err)
}
}
})
}
}
kin-openapi-0.124.0/openapi3filter/validate_request.go 0000664 0000000 0000000 00000027742 14604223742 0022724 0 ustar 00root root 0000000 0000000 package openapi3filter
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"net/http"
"sort"
"github.com/getkin/kin-openapi/openapi3"
)
// ErrAuthenticationServiceMissing is returned when no authentication service
// is defined for the request validator
var ErrAuthenticationServiceMissing = errors.New("missing AuthenticationFunc")
// ErrInvalidRequired is returned when a required value of a parameter or request body is not defined.
var ErrInvalidRequired = errors.New("value is required but missing")
// ErrInvalidEmptyValue is returned when a value of a parameter or request body is empty while it's not allowed.
var ErrInvalidEmptyValue = errors.New("empty value is not allowed")
// ValidateRequest is used to validate the given input according to previous
// loaded OpenAPIv3 spec. If the input does not match the OpenAPIv3 spec, a
// non-nil error will be returned.
//
// Note: One can tune the behavior of uniqueItems: true verification
// by registering a custom function with openapi3.RegisterArrayUniqueItemsChecker
func ValidateRequest(ctx context.Context, input *RequestValidationInput) (err error) {
var me openapi3.MultiError
options := input.Options
if options == nil {
options = &Options{}
}
route := input.Route
operation := route.Operation
operationParameters := operation.Parameters
pathItemParameters := route.PathItem.Parameters
// Security
security := operation.Security
// If there aren't any security requirements for the operation
if security == nil {
// Use the global security requirements.
security = &route.Spec.Security
}
if security != nil {
if err = ValidateSecurityRequirements(ctx, input, *security); err != nil && !options.MultiError {
return
}
if err != nil {
me = append(me, err)
}
}
// For each parameter of the PathItem
for _, parameterRef := range pathItemParameters {
parameter := parameterRef.Value
if operationParameters != nil {
if override := operationParameters.GetByInAndName(parameter.In, parameter.Name); override != nil {
continue
}
}
if err = ValidateParameter(ctx, input, parameter); err != nil && !options.MultiError {
return
}
if err != nil {
me = append(me, err)
}
}
// For each parameter of the Operation
for _, parameter := range operationParameters {
if options.ExcludeRequestQueryParams && parameter.Value.In == openapi3.ParameterInQuery {
continue
}
if err = ValidateParameter(ctx, input, parameter.Value); err != nil && !options.MultiError {
return
}
if err != nil {
me = append(me, err)
}
}
// RequestBody
requestBody := operation.RequestBody
if requestBody != nil && !options.ExcludeRequestBody {
if err = ValidateRequestBody(ctx, input, requestBody.Value); err != nil && !options.MultiError {
return
}
if err != nil {
me = append(me, err)
}
}
if len(me) > 0 {
return me
}
return
}
// ValidateParameter validates a parameter's value by JSON schema.
// The function returns RequestError with a ParseError cause when unable to parse a value.
// The function returns RequestError with ErrInvalidRequired cause when a value of a required parameter is not defined.
// The function returns RequestError with ErrInvalidEmptyValue cause when a value of a required parameter is not defined.
// The function returns RequestError with a openapi3.SchemaError cause when a value is invalid by JSON schema.
func ValidateParameter(ctx context.Context, input *RequestValidationInput, parameter *openapi3.Parameter) error {
if parameter.Schema == nil && parameter.Content == nil {
// We have no schema for the parameter. Assume that everything passes
// a schema-less check, but this could also be an error. The OpenAPI
// validation allows this to happen.
return nil
}
options := input.Options
if options == nil {
options = &Options{}
}
var value interface{}
var err error
var found bool
var schema *openapi3.Schema
// Validation will ensure that we either have content or schema.
if parameter.Content != nil {
if value, schema, found, err = decodeContentParameter(parameter, input); err != nil {
return &RequestError{Input: input, Parameter: parameter, Err: err}
}
} else {
if value, found, err = decodeStyledParameter(parameter, input); err != nil {
return &RequestError{Input: input, Parameter: parameter, Err: err}
}
schema = parameter.Schema.Value
}
// Set default value if needed
if !options.SkipSettingDefaults && value == nil && schema != nil {
value = schema.Default
for _, subSchema := range schema.AllOf {
if subSchema.Value.Default != nil {
value = subSchema.Value.Default
break // This is not a validation of the schema itself, so use the first default value.
}
}
if value != nil {
req := input.Request
switch parameter.In {
case openapi3.ParameterInPath:
// Path parameters are required.
// Next check `parameter.Required && !found` will catch this.
case openapi3.ParameterInQuery:
q := req.URL.Query()
q.Add(parameter.Name, fmt.Sprintf("%v", value))
req.URL.RawQuery = q.Encode()
case openapi3.ParameterInHeader:
req.Header.Add(parameter.Name, fmt.Sprintf("%v", value))
case openapi3.ParameterInCookie:
req.AddCookie(&http.Cookie{
Name: parameter.Name,
Value: fmt.Sprintf("%v", value),
})
}
}
}
// Validate a parameter's value and presence.
if parameter.Required && !found {
return &RequestError{Input: input, Parameter: parameter, Reason: ErrInvalidRequired.Error(), Err: ErrInvalidRequired}
}
if isNilValue(value) {
if !parameter.AllowEmptyValue && found {
return &RequestError{Input: input, Parameter: parameter, Reason: ErrInvalidEmptyValue.Error(), Err: ErrInvalidEmptyValue}
}
return nil
}
if schema == nil {
// A parameter's schema is not defined so skip validation of a parameter's value.
return nil
}
var opts []openapi3.SchemaValidationOption
if options.MultiError {
opts = make([]openapi3.SchemaValidationOption, 0, 1)
opts = append(opts, openapi3.MultiErrors())
}
if options.customSchemaErrorFunc != nil {
opts = append(opts, openapi3.SetSchemaErrorMessageCustomizer(options.customSchemaErrorFunc))
}
if err = schema.VisitJSON(value, opts...); err != nil {
return &RequestError{Input: input, Parameter: parameter, Err: err}
}
return nil
}
const prefixInvalidCT = "header Content-Type has unexpected value"
// ValidateRequestBody validates data of a request's body.
//
// The function returns RequestError with ErrInvalidRequired cause when a value is required but not defined.
// The function returns RequestError with a openapi3.SchemaError cause when a value is invalid by JSON schema.
func ValidateRequestBody(ctx context.Context, input *RequestValidationInput, requestBody *openapi3.RequestBody) error {
var (
req = input.Request
data []byte
)
options := input.Options
if options == nil {
options = &Options{}
}
if req.Body != http.NoBody && req.Body != nil {
defer req.Body.Close()
var err error
if data, err = io.ReadAll(req.Body); err != nil {
return &RequestError{
Input: input,
RequestBody: requestBody,
Reason: "reading failed",
Err: err,
}
}
// Put the data back into the input
req.Body = nil
if req.GetBody != nil {
if req.Body, err = req.GetBody(); err != nil {
req.Body = nil
}
}
if req.Body == nil {
req.ContentLength = int64(len(data))
req.GetBody = func() (io.ReadCloser, error) {
return io.NopCloser(bytes.NewReader(data)), nil
}
req.Body, _ = req.GetBody() // no error return
}
}
if len(data) == 0 {
if requestBody.Required {
return &RequestError{Input: input, RequestBody: requestBody, Err: ErrInvalidRequired}
}
return nil
}
content := requestBody.Content
if len(content) == 0 {
// A request's body does not have declared content, so skip validation.
return nil
}
inputMIME := req.Header.Get(headerCT)
contentType := requestBody.Content.Get(inputMIME)
if contentType == nil {
return &RequestError{
Input: input,
RequestBody: requestBody,
Reason: fmt.Sprintf("%s %q", prefixInvalidCT, inputMIME),
}
}
if contentType.Schema == nil {
// A JSON schema that describes the received data is not declared, so skip validation.
return nil
}
encFn := func(name string) *openapi3.Encoding { return contentType.Encoding[name] }
mediaType, value, err := decodeBody(bytes.NewReader(data), req.Header, contentType.Schema, encFn)
if err != nil {
return &RequestError{
Input: input,
RequestBody: requestBody,
Reason: "failed to decode request body",
Err: err,
}
}
defaultsSet := false
opts := make([]openapi3.SchemaValidationOption, 0, 4) // 4 potential opts here
opts = append(opts, openapi3.VisitAsRequest())
if !options.SkipSettingDefaults {
opts = append(opts, openapi3.DefaultsSet(func() { defaultsSet = true }))
}
if options.MultiError {
opts = append(opts, openapi3.MultiErrors())
}
if options.customSchemaErrorFunc != nil {
opts = append(opts, openapi3.SetSchemaErrorMessageCustomizer(options.customSchemaErrorFunc))
}
if options.ExcludeReadOnlyValidations {
opts = append(opts, openapi3.DisableReadOnlyValidation())
}
// Validate JSON with the schema
if err := contentType.Schema.Value.VisitJSON(value, opts...); err != nil {
schemaId := getSchemaIdentifier(contentType.Schema)
schemaId = prependSpaceIfNeeded(schemaId)
return &RequestError{
Input: input,
RequestBody: requestBody,
Reason: fmt.Sprintf("doesn't match schema%s", schemaId),
Err: err,
}
}
if defaultsSet {
var err error
if data, err = encodeBody(value, mediaType); err != nil {
return &RequestError{
Input: input,
RequestBody: requestBody,
Reason: "rewriting failed",
Err: err,
}
}
// Put the data back into the input
if req.Body != nil {
req.Body.Close()
}
req.ContentLength = int64(len(data))
req.GetBody = func() (io.ReadCloser, error) {
return io.NopCloser(bytes.NewReader(data)), nil
}
req.Body, _ = req.GetBody() // no error return
}
return nil
}
// ValidateSecurityRequirements goes through multiple OpenAPI 3 security
// requirements in order and returns nil on the first valid requirement.
// If no requirement is met, errors are returned in order.
func ValidateSecurityRequirements(ctx context.Context, input *RequestValidationInput, srs openapi3.SecurityRequirements) error {
if len(srs) == 0 {
return nil
}
var errs []error
for _, sr := range srs {
if err := validateSecurityRequirement(ctx, input, sr); err != nil {
if len(errs) == 0 {
errs = make([]error, 0, len(srs))
}
errs = append(errs, err)
continue
}
return nil
}
return &SecurityRequirementsError{
SecurityRequirements: srs,
Errors: errs,
}
}
// validateSecurityRequirement validates a single OpenAPI 3 security requirement
func validateSecurityRequirement(ctx context.Context, input *RequestValidationInput, securityRequirement openapi3.SecurityRequirement) error {
names := make([]string, 0, len(securityRequirement))
for name := range securityRequirement {
names = append(names, name)
}
sort.Strings(names)
// Get authentication function
options := input.Options
if options == nil {
options = &Options{}
}
f := options.AuthenticationFunc
if f == nil {
return ErrAuthenticationServiceMissing
}
var securitySchemes openapi3.SecuritySchemes
if components := input.Route.Spec.Components; components != nil {
securitySchemes = components.SecuritySchemes
}
// For each scheme for the requirement
for _, name := range names {
var securityScheme *openapi3.SecurityScheme
if securitySchemes != nil {
if ref := securitySchemes[name]; ref != nil {
securityScheme = ref.Value
}
}
if securityScheme == nil {
return &RequestError{
Input: input,
Err: fmt.Errorf("security scheme %q is not declared", name),
}
}
scopes := securityRequirement[name]
if err := f(ctx, &AuthenticationInput{
RequestValidationInput: input,
SecuritySchemeName: name,
SecurityScheme: securityScheme,
Scopes: scopes,
}); err != nil {
return err
}
}
return nil
}
kin-openapi-0.124.0/openapi3filter/validate_request_example_test.go 0000664 0000000 0000000 00000005013 14604223742 0025461 0 ustar 00root root 0000000 0000000 package openapi3filter
import (
"context"
"errors"
"fmt"
"log"
"net/http"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/routers/gorillamux"
)
func ExampleAuthenticationFunc() {
const spec = `
openapi: 3.0.0
info:
title: 'Validator'
version: 0.0.1
components:
securitySchemes:
OAuth2:
type: oauth2
flows:
clientCredentials:
tokenUrl: /oauth2/token
scopes:
secrets.read: Ability to read secrets
secrets.write: Ability to write secrets
paths:
/secret:
post:
security:
- OAuth2:
- secrets.write
responses:
'200':
description: Ok
'401':
description: Unauthorized
`
var (
errUnauthenticated = errors.New("login required")
errForbidden = errors.New("permission denied")
)
userScopes := map[string][]string{
"Alice": {"secrets.read"},
"Bob": {"secrets.read", "secrets.write"},
}
authenticationFunc := func(_ context.Context, ai *AuthenticationInput) error {
user := ai.RequestValidationInput.Request.Header.Get("X-User")
if user == "" {
return errUnauthenticated
}
for _, requiredScope := range ai.Scopes {
var allowed bool
for _, scope := range userScopes[user] {
if scope == requiredScope {
allowed = true
break
}
}
if !allowed {
return errForbidden
}
}
return nil
}
loader := openapi3.NewLoader()
doc, _ := loader.LoadFromData([]byte(spec))
router, _ := gorillamux.NewRouter(doc)
validateRequest := func(req *http.Request) {
route, pathParams, _ := router.FindRoute(req)
validationInput := &RequestValidationInput{
Request: req,
PathParams: pathParams,
Route: route,
Options: &Options{
AuthenticationFunc: authenticationFunc,
},
}
err := ValidateRequest(context.TODO(), validationInput)
switch {
case errors.Is(err, errUnauthenticated):
fmt.Println("username is required")
case errors.Is(err, errForbidden):
fmt.Println("user is not allowed to perform this action")
case err == nil:
fmt.Println("ok")
default:
log.Fatal(err)
}
}
req1, _ := http.NewRequest(http.MethodPost, "/secret", nil)
req1.Header.Set("X-User", "Alice")
req2, _ := http.NewRequest(http.MethodPost, "/secret", nil)
req2.Header.Set("X-User", "Bob")
req3, _ := http.NewRequest(http.MethodPost, "/secret", nil)
validateRequest(req1)
validateRequest(req2)
validateRequest(req3)
// output:
// user is not allowed to perform this action
// ok
// username is required
}
kin-openapi-0.124.0/openapi3filter/validate_request_input.go 0000664 0000000 0000000 00000002224 14604223742 0024127 0 ustar 00root root 0000000 0000000 package openapi3filter
import (
"net/http"
"net/url"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/routers"
)
// A ContentParameterDecoder takes a parameter definition from the OpenAPI spec,
// and the value which we received for it. It is expected to return the
// value unmarshaled into an interface which can be traversed for
// validation, it should also return the schema to be used for validating the
// object, since there can be more than one in the content spec.
//
// If a query parameter appears multiple times, values[] will have more
// than one value, but for all other parameter types it should have just
// one.
type ContentParameterDecoder func(param *openapi3.Parameter, values []string) (interface{}, *openapi3.Schema, error)
type RequestValidationInput struct {
Request *http.Request
PathParams map[string]string
QueryParams url.Values
Route *routers.Route
Options *Options
ParamDecoder ContentParameterDecoder
}
func (input *RequestValidationInput) GetQueryParams() url.Values {
q := input.QueryParams
if q == nil {
q = input.Request.URL.Query()
input.QueryParams = q
}
return q
}
kin-openapi-0.124.0/openapi3filter/validate_request_test.go 0000664 0000000 0000000 00000037074 14604223742 0023762 0 ustar 00root root 0000000 0000000 package openapi3filter
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/routers"
"github.com/getkin/kin-openapi/routers/gorillamux"
legacyrouter "github.com/getkin/kin-openapi/routers/legacy"
)
func setupTestRouter(t *testing.T, spec string) routers.Router {
t.Helper()
loader := openapi3.NewLoader()
doc, err := loader.LoadFromData([]byte(spec))
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
router, err := gorillamux.NewRouter(doc)
require.NoError(t, err)
return router
}
func TestValidateRequest(t *testing.T) {
const spec = `
openapi: 3.0.0
info:
title: 'Validator'
version: 0.0.1
paths:
/category:
post:
parameters:
- name: category
in: query
schema:
type: string
required: true
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- subCategory
properties:
subCategory:
type: string
category:
type: string
default: Sweets
responses:
'201':
description: Created
security:
- apiKey: []
components:
securitySchemes:
apiKey:
type: apiKey
name: Api-Key
in: header
`
router := setupTestRouter(t, spec)
verifyAPIKeyPresence := func(c context.Context, input *AuthenticationInput) error {
if input.SecurityScheme.Type == "apiKey" {
var found bool
switch input.SecurityScheme.In {
case "query":
_, found = input.RequestValidationInput.GetQueryParams()[input.SecurityScheme.Name]
case "header":
_, found = input.RequestValidationInput.Request.Header[http.CanonicalHeaderKey(input.SecurityScheme.Name)]
case "cookie":
_, err := input.RequestValidationInput.Request.Cookie(input.SecurityScheme.Name)
found = !errors.Is(err, http.ErrNoCookie)
}
if !found {
return fmt.Errorf("%v not found in %v", input.SecurityScheme.Name, input.SecurityScheme.In)
}
}
return nil
}
type testRequestBody struct {
SubCategory string `json:"subCategory"`
Category string `json:"category,omitempty"`
}
type args struct {
requestBody *testRequestBody
url string
apiKey string
}
tests := []struct {
name string
args args
expectedModification bool
expectedErr error
}{
{
name: "Valid request with all fields set",
args: args{
requestBody: &testRequestBody{SubCategory: "Chocolate", Category: "Food"},
url: "/category?category=cookies",
apiKey: "SomeKey",
},
expectedModification: false,
expectedErr: nil,
},
{
name: "Valid request without certain fields",
args: args{
requestBody: &testRequestBody{SubCategory: "Chocolate"},
url: "/category?category=cookies",
apiKey: "SomeKey",
},
expectedModification: true,
expectedErr: nil,
},
{
name: "Invalid operation params",
args: args{
requestBody: &testRequestBody{SubCategory: "Chocolate"},
url: "/category?invalidCategory=badCookie",
apiKey: "SomeKey",
},
expectedModification: false,
expectedErr: &RequestError{},
},
{
name: "Invalid request body",
args: args{
requestBody: nil,
url: "/category?category=cookies",
apiKey: "SomeKey",
},
expectedModification: false,
expectedErr: &RequestError{},
},
{
name: "Invalid security",
args: args{
requestBody: &testRequestBody{SubCategory: "Chocolate"},
url: "/category?category=cookies",
apiKey: "",
},
expectedModification: false,
expectedErr: &SecurityRequirementsError{},
},
{
name: "Invalid request body and security",
args: args{
requestBody: nil,
url: "/category?category=cookies",
apiKey: "",
},
expectedModification: false,
expectedErr: &SecurityRequirementsError{},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
var requestBody io.Reader
var originalBodySize int
if tc.args.requestBody != nil {
testingBody, err := json.Marshal(tc.args.requestBody)
require.NoError(t, err)
requestBody = bytes.NewReader(testingBody)
originalBodySize = len(testingBody)
}
req, err := http.NewRequest(http.MethodPost, tc.args.url, requestBody)
require.NoError(t, err)
req.Header.Add("Content-Type", "application/json")
if tc.args.apiKey != "" {
req.Header.Add("Api-Key", tc.args.apiKey)
}
route, pathParams, err := router.FindRoute(req)
require.NoError(t, err)
validationInput := &RequestValidationInput{
Request: req,
PathParams: pathParams,
Route: route,
Options: &Options{
AuthenticationFunc: verifyAPIKeyPresence,
},
}
err = ValidateRequest(context.Background(), validationInput)
assert.IsType(t, tc.expectedErr, err, "ValidateRequest(): error = %v, expectedError %v", err, tc.expectedErr)
if tc.expectedErr != nil {
return
}
body, err := io.ReadAll(validationInput.Request.Body)
contentLen := int(validationInput.Request.ContentLength)
bodySize := len(body)
assert.NoError(t, err, "unable to read request body: %v", err)
assert.Equal(t, contentLen, bodySize, "expect ContentLength %d to equal body size %d", contentLen, bodySize)
bodyModified := originalBodySize != bodySize
assert.Equal(t, bodyModified, tc.expectedModification, "expect request body modification happened: %t, expected %t", bodyModified, tc.expectedModification)
validationInput.Request.Body, err = validationInput.Request.GetBody()
assert.NoError(t, err, "unable to re-generate body by GetBody(): %v", err)
body2, err := io.ReadAll(validationInput.Request.Body)
assert.NoError(t, err, "unable to read request body: %v", err)
assert.Equal(t, body, body2, "body by GetBody() is not matched")
})
}
}
func TestValidateQueryParams(t *testing.T) {
type testCase struct {
name string
param *openapi3.Parameter
query string
want map[string]interface{}
err *openapi3.SchemaError // test ParseError in decoder tests
}
testCases := []testCase{
{
name: "deepObject explode additionalProperties with object properties - missing required property",
param: &openapi3.Parameter{
Name: "param", In: "query", Style: "deepObject", Explode: explode,
Schema: objectOf(
"obj", additionalPropertiesObjectOf(func() *openapi3.SchemaRef {
s := objectOf(
"item1", integerSchema,
"requiredProp", stringSchema,
)
s.Value.Required = []string{"requiredProp"}
return s
}()),
"objIgnored", objectOf("items", stringArraySchema),
),
},
query: "param[obj][prop1][item1]=1",
err: &openapi3.SchemaError{SchemaField: "required", Reason: "property \"requiredProp\" is missing"},
},
{
// XXX should this error out?
name: "deepObject explode additionalProperties with object properties - extraneous nested param property ignored",
param: &openapi3.Parameter{
Name: "param", In: "query", Style: "deepObject", Explode: explode,
Schema: objectOf(
"obj", additionalPropertiesObjectOf(objectOf(
"item1", integerSchema,
"requiredProp", stringSchema,
)),
"objIgnored", objectOf("items", stringArraySchema),
),
},
query: "param[obj][prop1][inexistent]=1",
want: map[string]interface{}{
"obj": map[string]interface{}{
"prop1": map[string]interface{}{},
},
},
},
{
name: "deepObject explode additionalProperties with object properties",
param: &openapi3.Parameter{
Name: "param", In: "query", Style: "deepObject", Explode: explode,
Schema: objectOf(
"obj", additionalPropertiesObjectOf(objectOf(
"item1", numberSchema,
"requiredProp", stringSchema,
)),
"objIgnored", objectOf("items", stringArraySchema),
),
},
query: "param[obj][prop1][item1]=1.123",
want: map[string]interface{}{
"obj": map[string]interface{}{
"prop1": map[string]interface{}{
"item1": float64(1.123),
},
},
},
},
{
name: "deepObject explode nested objects - misplaced parameter",
param: &openapi3.Parameter{
Name: "param", In: "query", Style: "deepObject", Explode: explode,
Schema: objectOf(
"obj", objectOf("nestedObjOne", objectOf("items", stringArraySchema)),
),
},
query: "param[obj][nestedObjOne]=baz",
err: &openapi3.SchemaError{
SchemaField: "type", Reason: "value must be an object", Value: "baz", Schema: objectOf("items", stringArraySchema).Value,
},
},
{
name: "deepObject explode nested object - extraneous param ignored",
param: &openapi3.Parameter{
Name: "param", In: "query", Style: "deepObject", Explode: explode,
Schema: objectOf(
"obj", objectOf("nestedObjOne", stringSchema, "nestedObjTwo", stringSchema),
),
},
query: "anotherparam=bar",
want: map[string]interface{}(nil),
},
{
name: "deepObject explode additionalProperties with object properties - multiple properties",
param: &openapi3.Parameter{
Name: "param", In: "query", Style: "deepObject", Explode: explode,
Schema: objectOf(
"obj", additionalPropertiesObjectOf(objectOf("item1", integerSchema, "item2", stringArraySchema)),
"objIgnored", objectOf("items", stringArraySchema),
),
},
query: "param[obj][prop1][item1]=1¶m[obj][prop1][item2][0]=abc¶m[obj][prop2][item1]=2¶m[obj][prop2][item2][0]=def",
want: map[string]interface{}{
"obj": map[string]interface{}{
"prop1": map[string]interface{}{
"item1": int64(1),
"item2": []interface{}{"abc"},
},
"prop2": map[string]interface{}{
"item1": int64(2),
"item2": []interface{}{"def"},
},
},
},
},
//
//
{
name: "deepObject explode nested object anyOf",
param: &openapi3.Parameter{
Name: "param", In: "query", Style: "deepObject", Explode: explode,
Schema: objectOf(
"obj", anyofSchema,
),
},
query: "param[obj]=1",
want: map[string]interface{}{
"obj": int64(1),
},
},
{
name: "deepObject explode nested object allOf",
param: &openapi3.Parameter{
Name: "param", In: "query", Style: "deepObject", Explode: explode,
Schema: objectOf(
"obj", allofSchema,
),
},
query: "param[obj]=1",
want: map[string]interface{}{
"obj": int64(1),
},
},
{
name: "deepObject explode nested object oneOf",
param: &openapi3.Parameter{
Name: "param", In: "query", Style: "deepObject", Explode: explode,
Schema: objectOf(
"obj", oneofSchema,
),
},
query: "param[obj]=true",
want: map[string]interface{}{
"obj": true,
},
},
{
name: "deepObject explode nested object oneOf - object",
param: &openapi3.Parameter{
Name: "param", In: "query", Style: "deepObject", Explode: explode,
Schema: objectOf(
"obj", oneofSchemaObject,
),
},
query: "param[obj][id2]=1¶m[obj][name2]=abc",
want: map[string]interface{}{
"obj": map[string]interface{}{
"id2": "1",
"name2": "abc",
},
},
},
{
name: "deepObject explode nested object oneOf - object - more than one match",
param: &openapi3.Parameter{
Name: "param", In: "query", Style: "deepObject", Explode: explode,
Schema: objectOf(
"obj", oneofSchemaObject,
),
},
query: "param[obj][id]=1¶m[obj][id2]=2",
err: &openapi3.SchemaError{
SchemaField: "oneOf",
Value: map[string]interface{}{"id": "1", "id2": "2"},
Reason: "value matches more than one schema from \"oneOf\" (matches schemas at indices [0 1])",
Schema: oneofSchemaObject.Value,
},
},
{
name: "deepObject explode nested object oneOf - array",
param: &openapi3.Parameter{
Name: "param", In: "query", Style: "deepObject", Explode: explode,
Schema: objectOf(
"obj", oneofSchemaArrayObject,
),
},
query: "param[obj][0]=a¶m[obj][1]=b",
want: map[string]interface{}{
"obj": []interface{}{
"a",
"b",
},
},
},
//
//
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
info := &openapi3.Info{
Title: "MyAPI",
Version: "0.1",
}
doc := &openapi3.T{OpenAPI: "3.0.0", Info: info, Paths: openapi3.NewPaths()}
op := &openapi3.Operation{
OperationID: "test",
Parameters: []*openapi3.ParameterRef{{Value: tc.param}},
Responses: openapi3.NewResponses(),
}
doc.AddOperation("/test", http.MethodGet, op)
err := doc.Validate(context.Background())
require.NoError(t, err)
router, err := legacyrouter.NewRouter(doc)
require.NoError(t, err)
req, err := http.NewRequest(http.MethodGet, "http://test.org/test?"+tc.query, nil)
route, pathParams, err := router.FindRoute(req)
require.NoError(t, err)
input := &RequestValidationInput{Request: req, PathParams: pathParams, Route: route}
err = ValidateParameter(context.Background(), input, tc.param)
if tc.err != nil {
require.Error(t, err)
re, ok := err.(*RequestError)
if !ok {
t.Errorf("error is not a RequestError")
return
}
gErr, ok := re.Unwrap().(*openapi3.SchemaError)
if !ok {
t.Errorf("unknown RequestError wrapped error type")
}
matchSchemaError(t, gErr, tc.err)
return
}
require.NoError(t, err)
got, _, err := decodeStyledParameter(tc.param, input)
require.EqualValues(t, tc.want, got)
})
}
}
func matchSchemaError(t *testing.T, got, want error) {
t.Helper()
wErr, ok := want.(*openapi3.SchemaError)
if !ok {
t.Errorf("want error is not a SchemaError")
return
}
gErr, ok := got.(*openapi3.SchemaError)
if !ok {
t.Errorf("got error is not a SchemaError")
return
}
assert.Equalf(t, wErr.SchemaField, gErr.SchemaField, "SchemaError SchemaField differs")
assert.Equalf(t, wErr.Reason, gErr.Reason, "SchemaError Reason differs")
if wErr.Schema != nil {
assert.EqualValuesf(t, wErr.Schema, gErr.Schema, "SchemaError Schema differs")
}
if wErr.Value != nil {
assert.EqualValuesf(t, wErr.Value, gErr.Value, "SchemaError Value differs")
}
if gErr.Origin == nil && wErr.Origin != nil {
t.Errorf("expected error origin but got nothing")
}
if gErr.Origin != nil && wErr.Origin != nil {
switch gErrOrigin := gErr.Origin.(type) {
case *openapi3.SchemaError:
matchSchemaError(t, gErrOrigin, wErr.Origin)
case *ParseError:
matchParseError(t, gErrOrigin, wErr.Origin)
default:
t.Errorf("unknown origin error")
}
}
}
func TestValidateRequestExcludeQueryParams(t *testing.T) {
const spec = `
openapi: 3.0.0
info:
title: 'Validator'
version: 0.0.1
paths:
/category:
post:
parameters:
- name: category
in: query
schema:
type: integer
required: true
responses:
'200':
description: Ok
`
req, err := http.NewRequest(http.MethodPost, "/category?category=foo", nil)
require.NoError(t, err)
router := setupTestRouter(t, spec)
route, pathParams, err := router.FindRoute(req)
require.NoError(t, err)
err = ValidateRequest(context.Background(), &RequestValidationInput{
Request: req,
PathParams: pathParams,
Route: route,
Options: &Options{
ExcludeRequestQueryParams: true,
},
})
require.NoError(t, err)
err = ValidateRequest(context.Background(), &RequestValidationInput{
Request: req,
PathParams: pathParams,
Route: route,
Options: &Options{
ExcludeRequestQueryParams: false,
},
})
require.Error(t, err)
}
kin-openapi-0.124.0/openapi3filter/validate_response.go 0000664 0000000 0000000 00000014022 14604223742 0023055 0 ustar 00root root 0000000 0000000 // Package openapi3filter validates that requests and inputs request an OpenAPI 3 specification file.
package openapi3filter
import (
"bytes"
"context"
"fmt"
"io"
"net/http"
"sort"
"strings"
"github.com/getkin/kin-openapi/openapi3"
)
// ValidateResponse is used to validate the given input according to previous
// loaded OpenAPIv3 spec. If the input does not match the OpenAPIv3 spec, a
// non-nil error will be returned.
//
// Note: One can tune the behavior of uniqueItems: true verification
// by registering a custom function with openapi3.RegisterArrayUniqueItemsChecker
func ValidateResponse(ctx context.Context, input *ResponseValidationInput) error {
req := input.RequestValidationInput.Request
switch req.Method {
case "HEAD":
return nil
}
status := input.Status
// These status codes will never be validated.
// TODO: The list is probably missing some.
switch status {
case http.StatusNotModified,
http.StatusPermanentRedirect,
http.StatusTemporaryRedirect,
http.StatusMovedPermanently:
return nil
}
route := input.RequestValidationInput.Route
options := input.Options
if options == nil {
options = &Options{}
}
// Find input for the current status
responses := route.Operation.Responses
if responses.Len() == 0 {
return nil
}
responseRef := responses.Status(status) // Response
if responseRef == nil {
responseRef = responses.Default() // Default input
}
if responseRef == nil {
// By default, status that is not documented is allowed.
if !options.IncludeResponseStatus {
return nil
}
return &ResponseError{Input: input, Reason: "status is not supported"}
}
response := responseRef.Value
if response == nil {
return &ResponseError{Input: input, Reason: "response has not been resolved"}
}
opts := make([]openapi3.SchemaValidationOption, 0, 3) // 3 potential options here
if options.MultiError {
opts = append(opts, openapi3.MultiErrors())
}
if options.customSchemaErrorFunc != nil {
opts = append(opts, openapi3.SetSchemaErrorMessageCustomizer(options.customSchemaErrorFunc))
}
if options.ExcludeWriteOnlyValidations {
opts = append(opts, openapi3.DisableWriteOnlyValidation())
}
headers := make([]string, 0, len(response.Headers))
for k := range response.Headers {
if k != headerCT {
headers = append(headers, k)
}
}
sort.Strings(headers)
for _, headerName := range headers {
headerRef := response.Headers[headerName]
if err := validateResponseHeader(headerName, headerRef, input, opts); err != nil {
return err
}
}
if options.ExcludeResponseBody {
// A user turned off validation of a response's body.
return nil
}
content := response.Content
if len(content) == 0 || options.ExcludeResponseBody {
// An operation does not contains a validation schema for responses with this status code.
return nil
}
inputMIME := input.Header.Get(headerCT)
contentType := content.Get(inputMIME)
if contentType == nil {
return &ResponseError{
Input: input,
Reason: fmt.Sprintf("response %s: %q", prefixInvalidCT, inputMIME),
}
}
if contentType.Schema == nil {
// An operation does not contains a validation schema for responses with this status code.
return nil
}
// Read response's body.
body := input.Body
// Response would contain partial or empty input body
// after we begin reading.
// Ensure that this doesn't happen.
input.Body = nil
// Ensure we close the reader
defer body.Close()
// Read all
data, err := io.ReadAll(body)
if err != nil {
return &ResponseError{
Input: input,
Reason: "failed to read response body",
Err: err,
}
}
// Put the data back into the response.
input.SetBodyBytes(data)
encFn := func(name string) *openapi3.Encoding { return contentType.Encoding[name] }
_, value, err := decodeBody(bytes.NewBuffer(data), input.Header, contentType.Schema, encFn)
if err != nil {
return &ResponseError{
Input: input,
Reason: "failed to decode response body",
Err: err,
}
}
// Validate data with the schema.
if err := contentType.Schema.Value.VisitJSON(value, append(opts, openapi3.VisitAsResponse())...); err != nil {
schemaId := getSchemaIdentifier(contentType.Schema)
schemaId = prependSpaceIfNeeded(schemaId)
return &ResponseError{
Input: input,
Reason: fmt.Sprintf("response body doesn't match schema%s", schemaId),
Err: err,
}
}
return nil
}
func validateResponseHeader(headerName string, headerRef *openapi3.HeaderRef, input *ResponseValidationInput, opts []openapi3.SchemaValidationOption) error {
var err error
var decodedValue interface{}
var found bool
var sm *openapi3.SerializationMethod
dec := &headerParamDecoder{header: input.Header}
if sm, err = headerRef.Value.SerializationMethod(); err != nil {
return &ResponseError{
Input: input,
Reason: fmt.Sprintf("unable to get header %q serialization method", headerName),
Err: err,
}
}
if decodedValue, found, err = decodeValue(dec, headerName, sm, headerRef.Value.Schema, headerRef.Value.Required); err != nil {
return &ResponseError{
Input: input,
Reason: fmt.Sprintf("unable to decode header %q value", headerName),
Err: err,
}
}
if found {
if err = headerRef.Value.Schema.Value.VisitJSON(decodedValue, opts...); err != nil {
return &ResponseError{
Input: input,
Reason: fmt.Sprintf("response header %q doesn't match schema", headerName),
Err: err,
}
}
} else if headerRef.Value.Required {
return &ResponseError{
Input: input,
Reason: fmt.Sprintf("response header %q missing", headerName),
}
}
return nil
}
// getSchemaIdentifier gets something by which a schema could be identified.
// A schema by itself doesn't have a true identity field. This function makes
// a best effort to get a value that can fill that void.
func getSchemaIdentifier(schema *openapi3.SchemaRef) string {
var id string
if schema != nil {
id = strings.TrimSpace(schema.Ref)
}
if id == "" && schema.Value != nil {
id = strings.TrimSpace(schema.Value.Title)
}
return id
}
func prependSpaceIfNeeded(value string) string {
if len(value) > 0 {
value = " " + value
}
return value
}
kin-openapi-0.124.0/openapi3filter/validate_response_input.go 0000664 0000000 0000000 00000001463 14604223742 0024301 0 ustar 00root root 0000000 0000000 package openapi3filter
import (
"bytes"
"io"
"net/http"
)
type ResponseValidationInput struct {
RequestValidationInput *RequestValidationInput
Status int
Header http.Header
Body io.ReadCloser
Options *Options
}
func (input *ResponseValidationInput) SetBodyBytes(value []byte) *ResponseValidationInput {
input.Body = io.NopCloser(bytes.NewReader(value))
return input
}
var JSONPrefixes = []string{
")]}',\n",
}
// TrimJSONPrefix trims one of the possible prefixes
func TrimJSONPrefix(data []byte) []byte {
search:
for _, prefix := range JSONPrefixes {
if len(data) < len(prefix) {
continue
}
for i, b := range data[:len(prefix)] {
if b != prefix[i] {
continue search
}
}
return data[len(prefix):]
}
return data
}
kin-openapi-0.124.0/openapi3filter/validate_response_test.go 0000664 0000000 0000000 00000013600 14604223742 0024115 0 ustar 00root root 0000000 0000000 package openapi3filter
import (
"io"
"net/http"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
)
func Test_validateResponseHeader(t *testing.T) {
type args struct {
headerName string
headerRef *openapi3.HeaderRef
}
tests := []struct {
name string
args args
isHeaderPresent bool
headerVals []string
wantErr bool
wantErrMsg string
}{
{
name: "test required string header with single string value",
args: args{
headerName: "X-Blab",
headerRef: newHeaderRef(openapi3.NewStringSchema(), true),
},
isHeaderPresent: true,
headerVals: []string{"blab"},
wantErr: false,
},
{
name: "test required string header with single, empty string value",
args: args{
headerName: "X-Blab",
headerRef: newHeaderRef(openapi3.NewStringSchema(), true),
},
isHeaderPresent: true,
headerVals: []string{""},
wantErr: true,
wantErrMsg: `response header "X-Blab" doesn't match schema: Value is not nullable`,
},
{
name: "test optional string header with single string value",
args: args{
headerName: "X-Blab",
headerRef: newHeaderRef(openapi3.NewStringSchema(), false),
},
isHeaderPresent: false,
headerVals: []string{"blab"},
wantErr: false,
},
{
name: "test required, but missing string header",
args: args{
headerName: "X-Blab",
headerRef: newHeaderRef(openapi3.NewStringSchema(), true),
},
isHeaderPresent: false,
headerVals: nil,
wantErr: true,
wantErrMsg: `response header "X-Blab" missing`,
},
{
name: "test integer header with single integer value",
args: args{
headerName: "X-Blab",
headerRef: newHeaderRef(openapi3.NewIntegerSchema(), true),
},
isHeaderPresent: true,
headerVals: []string{"88"},
wantErr: false,
},
{
name: "test integer header with single string value",
args: args{
headerName: "X-Blab",
headerRef: newHeaderRef(openapi3.NewIntegerSchema(), true),
},
isHeaderPresent: true,
headerVals: []string{"blab"},
wantErr: true,
wantErrMsg: `unable to decode header "X-Blab" value: value blab: an invalid integer: invalid syntax`,
},
{
name: "test int64 header with single int64 value",
args: args{
headerName: "X-Blab",
headerRef: newHeaderRef(openapi3.NewInt64Schema(), true),
},
isHeaderPresent: true,
headerVals: []string{"88"},
wantErr: false,
},
{
name: "test int32 header with single int32 value",
args: args{
headerName: "X-Blab",
headerRef: newHeaderRef(openapi3.NewInt32Schema(), true),
},
isHeaderPresent: true,
headerVals: []string{"88"},
wantErr: false,
},
{
name: "test float64 header with single float64 value",
args: args{
headerName: "X-Blab",
headerRef: newHeaderRef(openapi3.NewFloat64Schema(), true),
},
isHeaderPresent: true,
headerVals: []string{"88.87"},
wantErr: false,
},
{
name: "test integer header with multiple csv integer values",
args: args{
headerName: "X-blab",
headerRef: newHeaderRef(newArraySchema(openapi3.NewIntegerSchema()), true),
},
isHeaderPresent: true,
headerVals: []string{"87,88"},
wantErr: false,
},
{
name: "test integer header with multiple integer values",
args: args{
headerName: "X-blab",
headerRef: newHeaderRef(newArraySchema(openapi3.NewIntegerSchema()), true),
},
isHeaderPresent: true,
headerVals: []string{"87", "88"},
wantErr: false,
},
{
name: "test non-typed, nullable header with single string value",
args: args{
headerName: "X-blab",
headerRef: newHeaderRef(&openapi3.Schema{Nullable: true}, true),
},
isHeaderPresent: true,
headerVals: []string{"blab"},
wantErr: false,
},
{
name: "test required non-typed, nullable header not present",
args: args{
headerName: "X-blab",
headerRef: newHeaderRef(&openapi3.Schema{Nullable: true}, true),
},
isHeaderPresent: false,
headerVals: []string{"blab"},
wantErr: true,
wantErrMsg: `response header "X-blab" missing`,
},
{
name: "test non-typed, non-nullable header with single string value",
args: args{
headerName: "X-blab",
headerRef: newHeaderRef(&openapi3.Schema{Nullable: false}, true),
},
isHeaderPresent: true,
headerVals: []string{"blab"},
wantErr: true,
wantErrMsg: `response header "X-blab" doesn't match schema: Value is not nullable`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
input := newInputDefault()
opts := []openapi3.SchemaValidationOption(nil)
if tt.isHeaderPresent {
input.Header = map[string][]string{http.CanonicalHeaderKey(tt.args.headerName): tt.headerVals}
}
err := validateResponseHeader(tt.args.headerName, tt.args.headerRef, input, opts)
if tt.wantErr {
require.NotEmpty(t, tt.wantErrMsg, "wanted error message is not populated")
require.Error(t, err)
require.ErrorContains(t, err, tt.wantErrMsg)
} else {
require.NoError(t, err)
}
})
}
}
func newInputDefault() *ResponseValidationInput {
return &ResponseValidationInput{
RequestValidationInput: &RequestValidationInput{
Request: nil,
PathParams: nil,
Route: nil,
},
Status: 200,
Header: nil,
Body: io.NopCloser(strings.NewReader(`{}`)),
}
}
func newHeaderRef(schema *openapi3.Schema, required bool) *openapi3.HeaderRef {
return &openapi3.HeaderRef{Value: &openapi3.Header{Parameter: openapi3.Parameter{Schema: &openapi3.SchemaRef{Value: schema}, Required: required}}}
}
func newArraySchema(schema *openapi3.Schema) *openapi3.Schema {
arraySchema := openapi3.NewArraySchema()
arraySchema.Items = openapi3.NewSchemaRef("", schema)
return arraySchema
}
kin-openapi-0.124.0/openapi3filter/validate_set_default_test.go 0000664 0000000 0000000 00000047012 14604223742 0024562 0 ustar 00root root 0000000 0000000 package openapi3filter
import (
"bytes"
"encoding/json"
"io"
"net/http"
"net/url"
"testing"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
legacyrouter "github.com/getkin/kin-openapi/routers/legacy"
)
func TestValidatingRequestParameterAndSetDefault(t *testing.T) {
const spec = `{
"openapi": "3.0.3",
"info": {
"version": "1.0.0",
"title": "title",
"description": "desc",
"contact": {
"email": "email"
}
},
"paths": {
"/accounts": {
"get": {
"description": "Create a new account",
"parameters": [
{
"in": "query",
"name": "q1",
"schema": {
"type": "string",
"default": "Q"
}
},
{
"in": "query",
"name": "q2",
"schema": {
"type": "string",
"default": "Q"
}
},
{
"in": "query",
"name": "q3",
"schema": {
"type": "string"
}
},
{
"in": "header",
"name": "h1",
"schema": {
"type": "boolean",
"default": true
}
},
{
"in": "header",
"name": "h2",
"schema": {
"type": "boolean",
"default": true
}
},
{
"in": "header",
"name": "h3",
"schema": {
"type": "boolean"
}
},
{
"in": "cookie",
"name": "c1",
"schema": {
"type": "integer",
"default": 128
}
},
{
"in": "cookie",
"name": "c2",
"schema": {
"type": "integer",
"default": 128
}
},
{
"in": "cookie",
"name": "c3",
"schema": {
"type": "integer"
}
}
],
"responses": {
"201": {
"description": "Successfully created a new account"
},
"400": {
"description": "The server could not understand the request due to invalid syntax",
}
}
}
}
}
}
`
sl := openapi3.NewLoader()
doc, err := sl.LoadFromData([]byte(spec))
require.NoError(t, err)
err = doc.Validate(sl.Context)
require.NoError(t, err)
router, err := legacyrouter.NewRouter(doc)
require.NoError(t, err)
httpReq, err := http.NewRequest(http.MethodGet, "/accounts", nil)
require.NoError(t, err)
params := &url.Values{
"q2": []string{"from_request"},
}
httpReq.URL.RawQuery = params.Encode()
httpReq.Header.Set("h2", "false")
httpReq.AddCookie(&http.Cookie{Name: "c2", Value: "1024"})
route, pathParams, err := router.FindRoute(httpReq)
require.NoError(t, err)
err = ValidateRequest(sl.Context, &RequestValidationInput{
Request: httpReq,
PathParams: pathParams,
Route: route,
})
require.NoError(t, err)
// Unset default values in URL were set
require.Equal(t, "Q", httpReq.URL.Query().Get("q1"))
// Unset default values in headers were set
require.Equal(t, "true", httpReq.Header.Get("h1"))
// Unset default values in cookies were set
cookie, err := httpReq.Cookie("c1")
require.NoError(t, err)
require.Equal(t, "128", cookie.Value)
// All values from request were retained
require.Equal(t, "from_request", httpReq.URL.Query().Get("q2"))
require.Equal(t, "false", httpReq.Header.Get("h2"))
cookie, err = httpReq.Cookie("c2")
require.NoError(t, err)
require.Equal(t, "1024", cookie.Value)
// Not set value to parameters without default value
require.Equal(t, "", httpReq.URL.Query().Get("q3"))
require.Equal(t, "", httpReq.Header.Get("h3"))
_, err = httpReq.Cookie("c3")
require.Equal(t, http.ErrNoCookie, err)
}
func TestValidateRequestBodyAndSetDefault(t *testing.T) {
const spec = `{
"openapi": "3.0.3",
"info": {
"version": "1.0.0",
"title": "title",
"description": "desc",
"contact": {
"email": "email"
}
},
"paths": {
"/accounts": {
"post": {
"description": "Create a new account",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["id"],
"properties": {
"id": {
"type": "string",
"pattern": "[0-9a-v]+$",
"minLength": 20,
"maxLength": 20
},
"name": {
"type": "string",
"default": "default"
},
"code": {
"type": "integer",
"default": 123
},
"all": {
"type": "boolean",
"default": false
},
"page": {
"type": "object",
"properties": {
"num": {
"type": "integer",
"default": 1
},
"size": {
"type": "integer",
"default": 10
},
"order": {
"type": "string",
"enum": ["asc", "desc"],
"default": "desc"
}
}
},
"filters": {
"type": "array",
"nullable": true,
"items": {
"type": "object",
"properties": {
"field": {
"type": "string",
"default": "name"
},
"op": {
"type": "string",
"enum": ["eq", "ne"],
"default": "eq"
},
"value": {
"type": "integer",
"default": 123
}
}
}
},
"social_network": {
"oneOf": [
{
"type": "object",
"required": ["platform"],
"properties": {
"platform": {
"type": "string",
"enum": [
"twitter"
]
},
"tw_link": {
"type": "string",
"default": "www.twitter.com"
}
}
},
{
"type": "object",
"required": ["platform"],
"properties": {
"platform": {
"type": "string",
"enum": [
"facebook"
]
},
"fb_link": {
"type": "string",
"default": "www.facebook.com"
}
}
}
]
},
"social_network_2": {
"anyOf": [
{
"type": "object",
"required": ["platform"],
"properties": {
"platform": {
"type": "string",
"enum": [
"twitter"
]
},
"tw_link": {
"type": "string",
"default": "www.twitter.com"
}
}
},
{
"type": "object",
"required": ["platform"],
"properties": {
"platform": {
"type": "string",
"enum": [
"facebook"
]
},
"fb_link": {
"type": "string",
"default": "www.facebook.com"
}
}
}
]
},
"contact": {
"oneOf": [
{
"type": "object",
"required": ["email"],
"properties": {
"email": {
"type": "string"
},
"allow_image": {
"type": "boolean",
"default": true
}
},
"additionalProperties": false
},
{
"type": "object",
"required": ["phone"],
"properties": {
"phone": {
"type": "string"
},
"allow_text": {
"type": "boolean",
"default": false
}
},
"additionalProperties": false
}
]
},
"contact2": {
"anyOf": [
{
"type": "object",
"required": ["email"],
"properties": {
"email": {
"type": "string"
},
"allow_image": {
"type": "boolean",
"default": true
}
},
"additionalProperties": false
},
{
"type": "object",
"required": ["phone"],
"properties": {
"phone": {
"type": "string"
},
"allow_text": {
"type": "boolean",
"default": false
}
},
"additionalProperties": false
}
]
}
}
}
}
}
},
"responses": {
"201": {
"description": "Successfully created a new account"
},
"400": {
"description": "The server could not understand the request due to invalid syntax",
}
}
}
}
}
}`
sl := openapi3.NewLoader()
doc, err := sl.LoadFromData([]byte(spec))
require.NoError(t, err)
err = doc.Validate(sl.Context)
require.NoError(t, err)
router, err := legacyrouter.NewRouter(doc)
require.NoError(t, err)
type page struct {
Num int `json:"num,omitempty"`
Size int `json:"size,omitempty"`
Order string `json:"order,omitempty"`
}
type filter struct {
Field string `json:"field,omitempty"`
OP string `json:"op,omitempty"`
Value int `json:"value,omitempty"`
}
type socialNetwork struct {
Platform string `json:"platform,omitempty"`
FBLink string `json:"fb_link,omitempty"`
TWLink string `json:"tw_link,omitempty"`
}
type contact struct {
Email string `json:"email,omitempty"`
Phone string `json:"phone,omitempty"`
}
type body struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Code int `json:"code,omitempty"`
All bool `json:"all,omitempty"`
Page *page `json:"page,omitempty"`
Filters []filter `json:"filters,omitempty"`
SocialNetwork *socialNetwork `json:"social_network,omitempty"`
SocialNetwork2 *socialNetwork `json:"social_network_2,omitempty"`
Contact *contact `json:"contact,omitempty"`
Contact2 *contact `json:"contact2,omitempty"`
}
testCases := []struct {
name string
body body
bodyAssertion func(t *testing.T, body string)
}{
{
name: "only id",
body: body{
ID: "bt6kdc3d0cvp6u8u3ft0",
},
bodyAssertion: func(t *testing.T, body string) {
require.JSONEq(t, `{"id":"bt6kdc3d0cvp6u8u3ft0", "name": "default", "code": 123, "all": false}`, body)
},
},
{
name: "id & name",
body: body{
ID: "bt6kdc3d0cvp6u8u3ft0",
Name: "non-default",
},
bodyAssertion: func(t *testing.T, body string) {
require.JSONEq(t, `{"id":"bt6kdc3d0cvp6u8u3ft0", "name": "non-default", "code": 123, "all": false}`, body)
},
},
{
name: "id & name & code",
body: body{
ID: "bt6kdc3d0cvp6u8u3ft0",
Name: "non-default",
Code: 456,
},
bodyAssertion: func(t *testing.T, body string) {
require.JSONEq(t, `{"id":"bt6kdc3d0cvp6u8u3ft0", "name": "non-default", "code": 456, "all": false}`, body)
},
},
{
name: "id & name & code & all",
body: body{
ID: "bt6kdc3d0cvp6u8u3ft0",
Name: "non-default",
Code: 456,
All: true,
},
bodyAssertion: func(t *testing.T, body string) {
require.JSONEq(t, `{"id":"bt6kdc3d0cvp6u8u3ft0", "name": "non-default", "code": 456, "all": true}`, body)
},
},
{
name: "id & page(num)",
body: body{
ID: "bt6kdc3d0cvp6u8u3ft0",
Page: &page{
Num: 10,
},
},
bodyAssertion: func(t *testing.T, body string) {
require.JSONEq(t, `
{
"id": "bt6kdc3d0cvp6u8u3ft0",
"name": "default",
"code": 123,
"all": false,
"page": {
"num": 10,
"size": 10,
"order": "desc"
}
}
`, body)
},
},
{
name: "id & page(num & order)",
body: body{
ID: "bt6kdc3d0cvp6u8u3ft0",
Page: &page{
Num: 10,
Order: "asc",
},
},
bodyAssertion: func(t *testing.T, body string) {
require.JSONEq(t, `
{
"id": "bt6kdc3d0cvp6u8u3ft0",
"name": "default",
"code": 123,
"all": false,
"page": {
"num": 10,
"size": 10,
"order": "asc"
}
}
`, body)
},
},
{
name: "id & page & filters(one element and contains field)",
body: body{
ID: "bt6kdc3d0cvp6u8u3ft0",
Page: &page{
Num: 10,
Order: "asc",
},
Filters: []filter{
{
Field: "code",
},
},
},
bodyAssertion: func(t *testing.T, body string) {
require.JSONEq(t, `
{
"id": "bt6kdc3d0cvp6u8u3ft0",
"name": "default",
"code": 123,
"all": false,
"page": {
"num": 10,
"size": 10,
"order": "asc"
},
"filters": [
{
"field": "code",
"op": "eq",
"value": 123
}
]
}
`, body)
},
},
{
name: "id & page & filters(one element and contains field & op & value)",
body: body{
ID: "bt6kdc3d0cvp6u8u3ft0",
Page: &page{
Num: 10,
Order: "asc",
},
Filters: []filter{
{
Field: "code",
OP: "ne",
Value: 456,
},
},
},
bodyAssertion: func(t *testing.T, body string) {
require.JSONEq(t, `
{
"id": "bt6kdc3d0cvp6u8u3ft0",
"name": "default",
"code": 123,
"all": false,
"page": {
"num": 10,
"size": 10,
"order": "asc"
},
"filters": [
{
"field": "code",
"op": "ne",
"value": 456
}
]
}
`, body)
},
},
{
name: "id & page & filters(multiple elements)",
body: body{
ID: "bt6kdc3d0cvp6u8u3ft0",
Page: &page{
Num: 10,
Order: "asc",
},
Filters: []filter{
{
Value: 456,
},
{
OP: "ne",
},
{
Field: "code",
Value: 456,
},
{
OP: "ne",
Value: 789,
},
{
Field: "code",
OP: "ne",
Value: 456,
},
},
},
bodyAssertion: func(t *testing.T, body string) {
require.JSONEq(t, `
{
"id": "bt6kdc3d0cvp6u8u3ft0",
"name": "default",
"code": 123,
"all": false,
"page": {
"num": 10,
"size": 10,
"order": "asc"
},
"filters": [
{
"field": "name",
"op": "eq",
"value": 456
},
{
"field": "name",
"op": "ne",
"value": 123
},
{
"field": "code",
"op": "eq",
"value": 456
},
{
"field": "name",
"op": "ne",
"value": 789
},
{
"field": "code",
"op": "ne",
"value": 456
}
]
}
`, body)
},
},
{
name: "social_network(oneOf)",
body: body{
ID: "bt6kdc3d0cvp6u8u3ft0",
SocialNetwork: &socialNetwork{
Platform: "facebook",
},
},
bodyAssertion: func(t *testing.T, body string) {
require.JSONEq(t, `
{
"id": "bt6kdc3d0cvp6u8u3ft0",
"name": "default",
"code": 123,
"all": false,
"social_network": {
"platform": "facebook",
"fb_link": "www.facebook.com"
}
}
`, body)
},
},
{
name: "social_network_2(anyOf)",
body: body{
ID: "bt6kdc3d0cvp6u8u3ft0",
SocialNetwork2: &socialNetwork{
Platform: "facebook",
},
},
bodyAssertion: func(t *testing.T, body string) {
require.JSONEq(t, `
{
"id": "bt6kdc3d0cvp6u8u3ft0",
"name": "default",
"code": 123,
"all": false,
"social_network_2": {
"platform": "facebook",
"fb_link": "www.facebook.com"
}
}
`, body)
},
},
{
name: "contact(oneOf)",
body: body{
ID: "bt6kdc3d0cvp6u8u3ft0",
Contact: &contact{
Phone: "123456",
},
},
bodyAssertion: func(t *testing.T, body string) {
require.JSONEq(t, `
{
"id": "bt6kdc3d0cvp6u8u3ft0",
"name": "default",
"code": 123,
"all": false,
"contact": {
"phone": "123456",
"allow_text": false
}
}
`, body)
},
},
{
name: "contact(anyOf)",
body: body{
ID: "bt6kdc3d0cvp6u8u3ft0",
Contact2: &contact{
Phone: "123456",
},
},
bodyAssertion: func(t *testing.T, body string) {
require.JSONEq(t, `
{
"id": "bt6kdc3d0cvp6u8u3ft0",
"name": "default",
"code": 123,
"all": false,
"contact2": {
"phone": "123456",
"allow_text": false
}
}
`, body)
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
b, err := json.Marshal(tc.body)
require.NoError(t, err)
httpReq, err := http.NewRequest(http.MethodPost, "/accounts", bytes.NewReader(b))
require.NoError(t, err)
httpReq.Header.Add(headerCT, "application/json")
route, pathParams, err := router.FindRoute(httpReq)
require.NoError(t, err)
err = ValidateRequest(sl.Context, &RequestValidationInput{
Request: httpReq,
PathParams: pathParams,
Route: route,
})
require.NoError(t, err)
validatedReqBody, err := io.ReadAll(httpReq.Body)
require.NoError(t, err)
tc.bodyAssertion(t, string(validatedReqBody))
})
}
}
kin-openapi-0.124.0/openapi3filter/validation_discriminator_test.go 0000664 0000000 0000000 00000004257 14604223742 0025477 0 ustar 00root root 0000000 0000000 package openapi3filter
import (
"bytes"
"net/http"
"testing"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
legacyrouter "github.com/getkin/kin-openapi/routers/legacy"
)
func TestValidationWithDiscriminatorSelection(t *testing.T) {
const spec = `
openapi: 3.0.0
info:
version: 0.2.0
title: yaAPI
paths:
/blob:
put:
operationId: SetObj
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/blob'
responses:
'200':
description: Ok
components:
schemas:
blob:
oneOf:
- $ref: '#/components/schemas/objA'
- $ref: '#/components/schemas/objB'
discriminator:
propertyName: discr
mapping:
objA: '#/components/schemas/objA'
objB: '#/components/schemas/objB'
genericObj:
type: object
required:
- discr
properties:
discr:
type: string
enum:
- objA
- objB
discriminator:
propertyName: discr
mapping:
objA: '#/components/schemas/objA'
objB: '#/components/schemas/objB'
objA:
allOf:
- $ref: '#/components/schemas/genericObj'
- type: object
properties:
base64:
type: string
objB:
allOf:
- $ref: '#/components/schemas/genericObj'
- type: object
properties:
value:
type: integer
`
loader := openapi3.NewLoader()
doc, err := loader.LoadFromData([]byte(spec))
require.NoError(t, err)
router, err := legacyrouter.NewRouter(doc)
require.NoError(t, err)
body := bytes.NewReader([]byte(`{"discr": "objA", "base64": "S25vY2sgS25vY2ssIE5lbyAuLi4="}`))
req, err := http.NewRequest("PUT", "/blob", body)
require.NoError(t, err)
req.Header.Add(headerCT, "application/json")
route, pathParams, err := router.FindRoute(req)
require.NoError(t, err)
requestValidationInput := &RequestValidationInput{
Request: req,
PathParams: pathParams,
Route: route,
}
err = ValidateRequest(loader.Context, requestValidationInput)
require.NoError(t, err)
}
kin-openapi-0.124.0/openapi3filter/validation_enum_test.go 0000664 0000000 0000000 00000011077 14604223742 0023572 0 ustar 00root root 0000000 0000000 package openapi3filter
import (
"bytes"
"net/http"
"testing"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
legacyrouter "github.com/getkin/kin-openapi/routers/legacy"
)
func TestValidationWithIntegerEnum(t *testing.T) {
t.Run("PUT Request", func(t *testing.T) {
const spec = `
openapi: 3.0.0
info:
title: Example integer enum
version: '0.1'
paths:
/sample:
put:
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
exenum:
type: integer
enum:
- 0
- 1
- 2
- 3
example: 0
nullable: true
responses:
'200':
description: Ok
`
loader := openapi3.NewLoader()
doc, err := loader.LoadFromData([]byte(spec))
require.NoError(t, err)
router, err := legacyrouter.NewRouter(doc)
require.NoError(t, err)
tests := []struct {
data []byte
wantErr bool
}{
{
[]byte(`{"exenum": 1}`),
false,
},
{
[]byte(`{"exenum": "1"}`),
true,
},
{
[]byte(`{"exenum": null}`),
false,
},
{
[]byte(`{}`),
false,
},
}
for _, tt := range tests {
body := bytes.NewReader(tt.data)
req, err := http.NewRequest("PUT", "/sample", body)
require.NoError(t, err)
req.Header.Add(headerCT, "application/json")
route, pathParams, err := router.FindRoute(req)
require.NoError(t, err)
requestValidationInput := &RequestValidationInput{
Request: req,
PathParams: pathParams,
Route: route,
}
err = ValidateRequest(loader.Context, requestValidationInput)
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
}
})
t.Run("GET Request", func(t *testing.T) {
const spec = `
openapi: 3.0.0
info:
title: Example integer enum
version: '0.1'
paths:
/sample:
get:
parameters:
- in: query
name: exenum
schema:
type: integer
enum:
- 0
- 1
- 2
- 3
responses:
'200':
description: Ok
`
loader := openapi3.NewLoader()
doc, err := loader.LoadFromData([]byte(spec))
require.NoError(t, err)
router, err := legacyrouter.NewRouter(doc)
require.NoError(t, err)
tests := []struct {
exenum string
wantErr bool
}{
{
"1",
false,
},
{
"4",
true,
},
}
for _, tt := range tests {
req, err := http.NewRequest("GET", "/sample?exenum="+tt.exenum, nil)
require.NoError(t, err)
route, pathParams, err := router.FindRoute(req)
require.NoError(t, err)
requestValidationInput := &RequestValidationInput{
Request: req,
PathParams: pathParams,
Route: route,
}
err = ValidateRequest(loader.Context, requestValidationInput)
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
}
})
}
func TestValidationWithStringEnum(t *testing.T) {
const spec = `
openapi: 3.0.0
info:
title: Example string enum
version: '0.1'
paths:
/sample:
put:
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
exenum:
type: string
enum:
- "0"
- "1"
- "2"
- "3"
example: "0"
responses:
'200':
description: Ok
`
loader := openapi3.NewLoader()
doc, err := loader.LoadFromData([]byte(spec))
require.NoError(t, err)
router, err := legacyrouter.NewRouter(doc)
require.NoError(t, err)
tests := []struct {
data []byte
wantErr bool
}{
{
[]byte(`{"exenum": "1"}`),
false,
},
{
[]byte(`{"exenum": 1}`),
true,
},
{
[]byte(`{"exenum": null}`),
true,
},
{
[]byte(`{}`),
false,
},
}
for _, tt := range tests {
body := bytes.NewReader(tt.data)
req, err := http.NewRequest("PUT", "/sample", body)
require.NoError(t, err)
req.Header.Add(headerCT, "application/json")
route, pathParams, err := router.FindRoute(req)
require.NoError(t, err)
requestValidationInput := &RequestValidationInput{
Request: req,
PathParams: pathParams,
Route: route,
}
err = ValidateRequest(loader.Context, requestValidationInput)
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
}
}
kin-openapi-0.124.0/openapi3filter/validation_error.go 0000664 0000000 0000000 00000005155 14604223742 0022720 0 ustar 00root root 0000000 0000000 package openapi3filter
import (
"bytes"
"strconv"
)
// ValidationError struct provides granular error information
// useful for communicating issues back to end user and developer.
// Based on https://jsonapi.org/format/#error-objects
type ValidationError struct {
// A unique identifier for this particular occurrence of the problem.
Id string `json:"id,omitempty" yaml:"id,omitempty"`
// The HTTP status code applicable to this problem.
Status int `json:"status,omitempty" yaml:"status,omitempty"`
// An application-specific error code, expressed as a string value.
Code string `json:"code,omitempty" yaml:"code,omitempty"`
// A short, human-readable summary of the problem. It **SHOULD NOT** change from occurrence to occurrence of the problem, except for purposes of localization.
Title string `json:"title,omitempty" yaml:"title,omitempty"`
// A human-readable explanation specific to this occurrence of the problem.
Detail string `json:"detail,omitempty" yaml:"detail,omitempty"`
// An object containing references to the source of the error
Source *ValidationErrorSource `json:"source,omitempty" yaml:"source,omitempty"`
}
// ValidationErrorSource struct
type ValidationErrorSource struct {
// A JSON Pointer [RFC6901] to the associated entity in the request document [e.g. \"/data\" for a primary data object, or \"/data/attributes/title\" for a specific attribute].
Pointer string `json:"pointer,omitempty" yaml:"pointer,omitempty"`
// A string indicating which query parameter caused the error.
Parameter string `json:"parameter,omitempty" yaml:"parameter,omitempty"`
}
var _ error = &ValidationError{}
// Error implements the error interface.
func (e *ValidationError) Error() string {
b := bytes.NewBufferString("[")
if e.Status != 0 {
b.WriteString(strconv.Itoa(e.Status))
}
b.WriteString("]")
b.WriteString("[")
if e.Code != "" {
b.WriteString(e.Code)
}
b.WriteString("]")
b.WriteString("[")
if e.Id != "" {
b.WriteString(e.Id)
}
b.WriteString("]")
b.WriteString(" ")
if e.Title != "" {
b.WriteString(e.Title)
b.WriteString(" ")
}
if e.Detail != "" {
b.WriteString("| ")
b.WriteString(e.Detail)
b.WriteString(" ")
}
if e.Source != nil {
b.WriteString("[source ")
if e.Source.Parameter != "" {
b.WriteString("parameter=")
b.WriteString(e.Source.Parameter)
} else if e.Source.Pointer != "" {
b.WriteString("pointer=")
b.WriteString(e.Source.Pointer)
}
b.WriteString("]")
}
if b.Len() == 0 {
return "no error"
}
return b.String()
}
// StatusCode implements the StatusCoder interface for DefaultErrorEncoder
func (e *ValidationError) StatusCode() int {
return e.Status
}
kin-openapi-0.124.0/openapi3filter/validation_error_encoder.go 0000664 0000000 0000000 00000012766 14604223742 0024425 0 ustar 00root root 0000000 0000000 package openapi3filter
import (
"context"
"fmt"
"net/http"
"strings"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/routers"
)
// ValidationErrorEncoder wraps a base ErrorEncoder to handle ValidationErrors
type ValidationErrorEncoder struct {
Encoder ErrorEncoder
}
// Encode implements the ErrorEncoder interface for encoding ValidationErrors
func (enc *ValidationErrorEncoder) Encode(ctx context.Context, err error, w http.ResponseWriter) {
enc.Encoder(ctx, ConvertErrors(err), w)
}
// ConvertErrors converts all errors to the appropriate error format.
func ConvertErrors(err error) error {
if e, ok := err.(*routers.RouteError); ok {
return convertRouteError(e)
}
e, ok := err.(*RequestError)
if !ok {
return err
}
var cErr *ValidationError
if e.Err == nil {
cErr = convertBasicRequestError(e)
} else if e.Err == ErrInvalidRequired {
cErr = convertErrInvalidRequired(e)
} else if e.Err == ErrInvalidEmptyValue {
cErr = convertErrInvalidEmptyValue(e)
} else if innerErr, ok := e.Err.(*ParseError); ok {
cErr = convertParseError(e, innerErr)
} else if innerErr, ok := e.Err.(*openapi3.SchemaError); ok {
cErr = convertSchemaError(e, innerErr)
}
if cErr != nil {
return cErr
}
return err
}
func convertRouteError(e *routers.RouteError) *ValidationError {
status := http.StatusNotFound
if e.Error() == routers.ErrMethodNotAllowed.Error() {
status = http.StatusMethodNotAllowed
}
return &ValidationError{Status: status, Title: e.Error()}
}
func convertBasicRequestError(e *RequestError) *ValidationError {
if strings.HasPrefix(e.Reason, prefixInvalidCT) {
if strings.HasSuffix(e.Reason, `""`) {
return &ValidationError{
Status: http.StatusUnsupportedMediaType,
Title: "header Content-Type is required",
}
}
return &ValidationError{
Status: http.StatusUnsupportedMediaType,
Title: prefixUnsupportedCT + strings.TrimPrefix(e.Reason, prefixInvalidCT),
}
}
return &ValidationError{
Status: http.StatusBadRequest,
Title: e.Error(),
}
}
func convertErrInvalidRequired(e *RequestError) *ValidationError {
if e.Err == ErrInvalidRequired && e.Parameter != nil {
return &ValidationError{
Status: http.StatusBadRequest,
Title: fmt.Sprintf("parameter %q in %s is required", e.Parameter.Name, e.Parameter.In),
}
}
return &ValidationError{
Status: http.StatusBadRequest,
Title: e.Error(),
}
}
func convertErrInvalidEmptyValue(e *RequestError) *ValidationError {
if e.Err == ErrInvalidEmptyValue && e.Parameter != nil {
return &ValidationError{
Status: http.StatusBadRequest,
Title: fmt.Sprintf("parameter %q in %s is not allowed to be empty", e.Parameter.Name, e.Parameter.In),
}
}
return &ValidationError{
Status: http.StatusBadRequest,
Title: e.Error(),
}
}
func convertParseError(e *RequestError, innerErr *ParseError) *ValidationError {
// We treat path params of the wrong type like a 404 instead of a 400
if innerErr.Kind == KindInvalidFormat && e.Parameter != nil && e.Parameter.In == "path" {
return &ValidationError{
Status: http.StatusNotFound,
Title: fmt.Sprintf("resource not found with %q value: %v", e.Parameter.Name, innerErr.Value),
}
} else if strings.HasPrefix(innerErr.Reason, prefixUnsupportedCT) {
return &ValidationError{
Status: http.StatusUnsupportedMediaType,
Title: innerErr.Reason,
}
} else if innerErr.RootCause() != nil {
if rootErr, ok := innerErr.Cause.(*ParseError); ok &&
rootErr.Kind == KindInvalidFormat && e.Parameter.In == "query" {
return &ValidationError{
Status: http.StatusBadRequest,
Title: fmt.Sprintf("parameter %q in %s is invalid: %v is %s",
e.Parameter.Name, e.Parameter.In, rootErr.Value, rootErr.Reason),
}
}
return &ValidationError{
Status: http.StatusBadRequest,
Title: innerErr.Reason,
}
}
return nil
}
func convertSchemaError(e *RequestError, innerErr *openapi3.SchemaError) *ValidationError {
cErr := &ValidationError{Title: innerErr.Reason}
// Handle "Origin" error
if originErr, ok := innerErr.Origin.(*openapi3.SchemaError); ok {
cErr = convertSchemaError(e, originErr)
}
// Add http status code
if e.Parameter != nil {
cErr.Status = http.StatusBadRequest
} else if e.RequestBody != nil {
cErr.Status = http.StatusUnprocessableEntity
}
// Add error source
if e.Parameter != nil {
// We have a JSONPointer in the query param too so need to
// make sure 'Parameter' check takes priority over 'Pointer'
cErr.Source = &ValidationErrorSource{Parameter: e.Parameter.Name}
} else if ptr := innerErr.JSONPointer(); ptr != nil {
cErr.Source = &ValidationErrorSource{Pointer: toJSONPointer(ptr)}
}
// Add details on allowed values for enums
if innerErr.SchemaField == "enum" {
enums := make([]string, 0, len(innerErr.Schema.Enum))
for _, enum := range innerErr.Schema.Enum {
enums = append(enums, fmt.Sprintf("%v", enum))
}
cErr.Detail = fmt.Sprintf("value %v at %s must be one of: %s",
innerErr.Value,
toJSONPointer(innerErr.JSONPointer()),
strings.Join(enums, ", "))
value := fmt.Sprintf("%v", innerErr.Value)
if e.Parameter != nil &&
(e.Parameter.Explode == nil || *e.Parameter.Explode) &&
(e.Parameter.Style == "" || e.Parameter.Style == "form") &&
strings.Contains(value, ",") {
parts := strings.Split(value, ",")
cErr.Detail = fmt.Sprintf("%s; perhaps you intended '?%s=%s'",
cErr.Detail,
e.Parameter.Name,
strings.Join(parts, "&"+e.Parameter.Name+"="))
}
}
return cErr
}
func toJSONPointer(reversePath []string) string {
return "/" + strings.Join(reversePath, "/")
}
kin-openapi-0.124.0/openapi3filter/validation_error_test.go 0000664 0000000 0000000 00000065700 14604223742 0023761 0 ustar 00root root 0000000 0000000 package openapi3filter
import (
"bytes"
"context"
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/routers"
)
func newPetstoreRequest(t *testing.T, method, path string, body io.Reader) *http.Request {
host := "petstore.swagger.io"
pathPrefix := "v2"
r, err := http.NewRequest(method, fmt.Sprintf("http://%s/%s%s", host, pathPrefix, path), body)
require.NoError(t, err)
r.Header.Set(headerCT, "application/json")
r.Header.Set("Authorization", "Bearer magicstring")
r.Host = host
return r
}
type validationFields struct {
Handler http.Handler
File string
ErrorEncoder ErrorEncoder
}
type validationArgs struct {
r *http.Request
}
type validationTest struct {
name string
fields validationFields
args validationArgs
wantErr bool
wantErrBody string
wantErrReason string
wantErrSchemaReason string
wantErrSchemaPath string
wantErrSchemaValue interface{}
wantErrSchemaOriginReason string
wantErrSchemaOriginPath string
wantErrSchemaOriginValue interface{}
wantErrParam string
wantErrParamIn string
wantErrParseKind ParseErrorKind
wantErrParseValue interface{}
wantErrParseReason string
wantErrResponse *ValidationError
}
func getValidationTests(t *testing.T) []*validationTest {
badHost, err := http.NewRequest(http.MethodGet, "http://unknown-host.com/v2/pet", nil)
require.NoError(t, err)
badPath := newPetstoreRequest(t, http.MethodGet, "/watdis", nil)
badMethod := newPetstoreRequest(t, http.MethodTrace, "/pet", nil)
missingBody1 := newPetstoreRequest(t, http.MethodPost, "/pet", nil)
missingBody2 := newPetstoreRequest(t, http.MethodPost, "/pet", bytes.NewBufferString(``))
noContentType := newPetstoreRequest(t, http.MethodPost, "/pet", bytes.NewBufferString(`{}`))
noContentType.Header.Del(headerCT)
noContentTypeNeeded := newPetstoreRequest(t, http.MethodGet, "/pet/findByStatus?status=sold", nil)
noContentTypeNeeded.Header.Del(headerCT)
unknownContentType := newPetstoreRequest(t, http.MethodPost, "/pet", bytes.NewBufferString(`{}`))
unknownContentType.Header.Set(headerCT, "application/xml")
unsupportedContentType := newPetstoreRequest(t, http.MethodPost, "/pet", bytes.NewBufferString(`{}`))
unsupportedContentType.Header.Set(headerCT, "text/plain")
unsupportedHeaderValue := newPetstoreRequest(t, http.MethodPost, "/pet", bytes.NewBufferString(`{}`))
unsupportedHeaderValue.Header.Set("x-environment", "watdis")
return []*validationTest{
//
// Basics
//
{
name: "error - unknown host",
args: validationArgs{
r: badHost,
},
wantErrReason: routers.ErrPathNotFound.Error(),
wantErrResponse: &ValidationError{Status: http.StatusNotFound, Title: routers.ErrPathNotFound.Error()},
},
{
name: "error - unknown path",
args: validationArgs{
r: badPath,
},
wantErrReason: routers.ErrPathNotFound.Error(),
wantErrResponse: &ValidationError{Status: http.StatusNotFound, Title: routers.ErrPathNotFound.Error()},
},
{
name: "error - unknown method",
args: validationArgs{
r: badMethod,
},
wantErrReason: routers.ErrMethodNotAllowed.Error(),
// TODO: By HTTP spec, this should have an Allow header with what is allowed
// but kin-openapi doesn't provide us the requested method or path, so impossible to provide details
wantErrResponse: &ValidationError{
Status: http.StatusMethodNotAllowed,
Title: routers.ErrMethodNotAllowed.Error(),
},
},
{
name: "error - missing body on POST",
args: validationArgs{
r: missingBody1,
},
wantErrBody: "request body has an error: " + ErrInvalidRequired.Error(),
wantErrResponse: &ValidationError{
Status: http.StatusBadRequest,
Title: "request body has an error: " + ErrInvalidRequired.Error(),
},
},
{
name: "error - empty body on POST",
args: validationArgs{
r: missingBody2,
},
wantErrBody: "request body has an error: " + ErrInvalidRequired.Error(),
wantErrResponse: &ValidationError{
Status: http.StatusBadRequest,
Title: "request body has an error: " + ErrInvalidRequired.Error(),
},
},
//
// Content-Type
//
{
name: "error - missing content-type on POST",
args: validationArgs{
r: noContentType,
},
wantErrReason: prefixInvalidCT + ` ""`,
wantErrResponse: &ValidationError{
Status: http.StatusUnsupportedMediaType,
Title: "header Content-Type is required",
},
},
{
name: "error - unknown content-type on POST",
args: validationArgs{
r: unknownContentType,
},
wantErrReason: "failed to decode request body",
wantErrParseKind: KindUnsupportedFormat,
wantErrParseReason: prefixUnsupportedCT + ` "application/xml"`,
wantErrResponse: &ValidationError{
Status: http.StatusUnsupportedMediaType,
Title: prefixUnsupportedCT + ` "application/xml"`,
},
},
{
name: "error - unsupported content-type on POST",
args: validationArgs{
r: unsupportedContentType,
},
wantErrReason: prefixInvalidCT + ` "text/plain"`,
wantErrResponse: &ValidationError{
Status: http.StatusUnsupportedMediaType,
Title: prefixUnsupportedCT + ` "text/plain"`,
},
},
{
name: "success - no content-type header required on GET",
args: validationArgs{
r: noContentTypeNeeded,
},
},
//
// Query strings
//
{
name: "empty deepobject query parameter",
args: validationArgs{
r: newPetstoreRequest(t, http.MethodGet, "/pet/filter", nil),
},
},
{
name: "deepobject query parameter type",
args: validationArgs{
r: newPetstoreRequest(t, http.MethodGet, "/pet/filter?deepFilter[booleans][0]=true&deepFilter[integers][0]=1&deepFilter[strings][0]=foo%26o&deepFilter[numbers][0]=1", nil),
},
},
{
name: "error - incorrect deepobject query parameter type bool",
args: validationArgs{
r: newPetstoreRequest(t, http.MethodGet, "/pet/filter?deepFilter[booleans][0]=notbool", nil),
},
wantErrParam: "deepFilter",
wantErrBody: "parameter \"deepFilter\" in query has an error: path booleans.0: value notbool: an invalid boolean: invalid syntax",
wantErrParamIn: "query",
wantErrResponse: &ValidationError{
Status: http.StatusBadRequest,
Title: "parameter \"deepFilter\" in query is invalid: notbool is an invalid boolean",
},
},
{
name: "error - incorrect deepobject query parameter type integer",
args: validationArgs{
r: newPetstoreRequest(t, http.MethodGet, "/pet/filter?deepFilter[integers][0]=1.234", nil),
},
wantErrParam: "deepFilter",
wantErrBody: "parameter \"deepFilter\" in query has an error: path integers.0: value 1.234: an invalid integer: invalid syntax",
wantErrParamIn: "query",
wantErrResponse: &ValidationError{
Status: http.StatusBadRequest,
Title: "parameter \"deepFilter\" in query is invalid: 1.234 is an invalid integer",
},
},
{
name: "error - incorrect deepobject query parameter type number",
args: validationArgs{
r: newPetstoreRequest(t, http.MethodGet, "/pet/filter?deepFilter[numbers][0]=aaa", nil),
},
wantErrParam: "deepFilter",
wantErrBody: "parameter \"deepFilter\" in query has an error: path numbers.0: value aaa: an invalid number: invalid syntax",
wantErrParamIn: "query",
wantErrResponse: &ValidationError{
Status: http.StatusBadRequest,
Title: "parameter \"deepFilter\" in query is invalid: aaa is an invalid number",
},
},
{
name: "error - missing required query string parameter",
args: validationArgs{
r: newPetstoreRequest(t, http.MethodGet, "/pet/findByStatus", nil),
},
wantErrParam: "status",
wantErrParamIn: "query",
wantErrBody: `parameter "status" in query has an error: value is required but missing`,
wantErrReason: "value is required but missing",
wantErrResponse: &ValidationError{
Status: http.StatusBadRequest,
Title: `parameter "status" in query is required`,
},
},
{
name: "error - wrong query string parameter type as integer",
args: validationArgs{
r: newPetstoreRequest(t, http.MethodGet, "/pet/findByIds?ids=1,notAnInt", nil),
},
wantErrParam: "ids",
wantErrParamIn: "query",
// This is a nested ParseError. The outer error is a KindOther with no details.
// So we'd need to look at the inner one which is a KindInvalidFormat. So just check the error body.
wantErrBody: `parameter "ids" in query has an error: path 1: value notAnInt: an invalid integer: invalid syntax`,
// TODO: Should we treat query params of the wrong type like a 404 instead of a 400?
wantErrResponse: &ValidationError{
Status: http.StatusBadRequest,
Title: `parameter "ids" in query is invalid: notAnInt is an invalid integer`,
},
},
{
name: "success - ignores unknown query string parameter",
args: validationArgs{
r: newPetstoreRequest(t, http.MethodGet, "/pet/findByStatus?status=available&wat=isdis", nil),
},
},
{
name: "error - non required query string has empty value",
args: validationArgs{
r: newPetstoreRequest(t, http.MethodGet, "/pets/?tags=", nil),
},
wantErrParam: "tags",
wantErrParamIn: "query",
wantErrBody: `parameter "tags" in query has an error: empty value is not allowed`,
wantErrReason: "empty value is not allowed",
wantErrResponse: &ValidationError{
Status: http.StatusBadRequest,
Title: `parameter "tags" in query is not allowed to be empty`,
},
},
{
name: "success - non required query string has empty value, but has AllowEmptyValue",
args: validationArgs{
r: newPetstoreRequest(t, http.MethodGet, "/pets/?status=", nil),
},
},
{
name: "success - normal case, query strings",
args: validationArgs{
r: newPetstoreRequest(t, http.MethodGet, "/pet/findByStatus?status=available", nil),
},
},
{
name: "success - normal case, query strings, array",
args: validationArgs{
r: newPetstoreRequest(t, http.MethodGet, "/pet/findByStatus?status=available&status=sold", nil),
},
},
{
name: "error - invalid query string array serialization",
args: validationArgs{
r: newPetstoreRequest(t, http.MethodGet, "/pet/findByStatus?status=available,sold", nil),
},
wantErrParam: "status",
wantErrParamIn: "query",
wantErrSchemaReason: "value is not one of the allowed values [\"available\",\"pending\",\"sold\"]",
wantErrSchemaPath: "/0",
wantErrSchemaValue: "available,sold",
wantErrResponse: &ValidationError{
Status: http.StatusBadRequest,
Title: "value is not one of the allowed values [\"available\",\"pending\",\"sold\"]",
Detail: "value available,sold at /0 must be one of: available, pending, sold; " +
// TODO: do we really want to use this heuristic to guess
// that they're using the wrong serialization?
"perhaps you intended '?status=available&status=sold'",
Source: &ValidationErrorSource{Parameter: "status"},
},
},
{
name: "error - invalid enum value for query string parameter",
args: validationArgs{
r: newPetstoreRequest(t, http.MethodGet, "/pet/findByStatus?status=sold&status=watdis", nil),
},
wantErrParam: "status",
wantErrParamIn: "query",
wantErrSchemaReason: "value is not one of the allowed values [\"available\",\"pending\",\"sold\"]",
wantErrSchemaPath: "/1",
wantErrSchemaValue: "watdis",
wantErrResponse: &ValidationError{
Status: http.StatusBadRequest,
Title: "value is not one of the allowed values [\"available\",\"pending\",\"sold\"]",
Detail: "value watdis at /1 must be one of: available, pending, sold",
Source: &ValidationErrorSource{Parameter: "status"},
},
},
{
name: "error - invalid enum value, allowing commas (without 'perhaps you intended' recommendation)",
args: validationArgs{
// fish,with,commas isn't an enum value
r: newPetstoreRequest(t, http.MethodGet, "/pet/findByKind?kind=dog|fish,with,commas", nil),
},
wantErrParam: "kind",
wantErrParamIn: "query",
wantErrSchemaReason: "value is not one of the allowed values [\"dog\",\"cat\",\"turtle\",\"bird,with,commas\"]",
wantErrSchemaPath: "/1",
wantErrSchemaValue: "fish,with,commas",
wantErrResponse: &ValidationError{
Status: http.StatusBadRequest,
Title: "value is not one of the allowed values [\"dog\",\"cat\",\"turtle\",\"bird,with,commas\"]",
Detail: "value fish,with,commas at /1 must be one of: dog, cat, turtle, bird,with,commas",
// No 'perhaps you intended' because its the right serialization format
Source: &ValidationErrorSource{Parameter: "kind"},
},
},
{
name: "success - valid enum value, allowing commas",
args: validationArgs{
r: newPetstoreRequest(t, http.MethodGet, "/pet/findByKind?kind=dog|bird,with,commas", nil),
},
},
//
// Request header params
//
{
name: "error - invalid enum value for header string parameter",
args: validationArgs{
r: unsupportedHeaderValue,
},
wantErrParam: "x-environment",
wantErrParamIn: "header",
wantErrSchemaReason: "value is not one of the allowed values [\"demo\",\"prod\"]",
wantErrSchemaPath: "/",
wantErrSchemaValue: "watdis",
wantErrResponse: &ValidationError{
Status: http.StatusBadRequest,
Title: "value is not one of the allowed values [\"demo\",\"prod\"]",
Detail: "value watdis at / must be one of: demo, prod",
Source: &ValidationErrorSource{Parameter: "x-environment"},
},
},
//
// Request bodies
//
{
name: "error - invalid enum value for header object attribute",
args: validationArgs{
r: newPetstoreRequest(t, http.MethodPost, "/pet", bytes.NewBufferString(`{"status":"watdis"}`)),
},
wantErrReason: "doesn't match schema #/components/schemas/PetWithRequired",
wantErrSchemaReason: "value is not one of the allowed values [\"available\",\"pending\",\"sold\"]",
wantErrSchemaValue: "watdis",
wantErrSchemaPath: "/status",
wantErrResponse: &ValidationError{
Status: http.StatusUnprocessableEntity,
Title: "value is not one of the allowed values [\"available\",\"pending\",\"sold\"]",
Detail: "value watdis at /status must be one of: available, pending, sold",
Source: &ValidationErrorSource{Pointer: "/status"},
},
},
{
name: "error - missing required object attribute",
args: validationArgs{
r: newPetstoreRequest(t, http.MethodPost, "/pet", bytes.NewBufferString(`{"name":"Bahama"}`)),
},
wantErrReason: "doesn't match schema #/components/schemas/PetWithRequired",
wantErrSchemaReason: `property "photoUrls" is missing`,
wantErrSchemaValue: map[string]string{"name": "Bahama"},
wantErrSchemaPath: "/photoUrls",
wantErrResponse: &ValidationError{
Status: http.StatusUnprocessableEntity,
Title: `property "photoUrls" is missing`,
Source: &ValidationErrorSource{Pointer: "/photoUrls"},
},
},
{
name: "error - missing required nested object attribute",
args: validationArgs{
r: newPetstoreRequest(t, http.MethodPost, "/pet",
bytes.NewBufferString(`{"name":"Bahama","photoUrls":[],"category":{}}`)),
},
wantErrReason: "doesn't match schema #/components/schemas/PetWithRequired",
wantErrSchemaReason: `property "name" is missing`,
wantErrSchemaValue: map[string]string{},
wantErrSchemaPath: "/category/name",
wantErrResponse: &ValidationError{
Status: http.StatusUnprocessableEntity,
Title: `property "name" is missing`,
Source: &ValidationErrorSource{Pointer: "/category/name"},
},
},
{
name: "error - missing required deeply nested object attribute",
args: validationArgs{
r: newPetstoreRequest(t, http.MethodPost, "/pet",
bytes.NewBufferString(`{"name":"Bahama","photoUrls":[],"category":{"tags": [{}]}}`)),
},
wantErrReason: "doesn't match schema #/components/schemas/PetWithRequired",
wantErrSchemaReason: `property "name" is missing`,
wantErrSchemaValue: map[string]string{},
wantErrSchemaPath: "/category/tags/0/name",
wantErrResponse: &ValidationError{
Status: http.StatusUnprocessableEntity,
Title: `property "name" is missing`,
Source: &ValidationErrorSource{Pointer: "/category/tags/0/name"},
},
},
{
name: "error - wrong attribute type",
args: validationArgs{
r: newPetstoreRequest(t, http.MethodPost, "/pet",
bytes.NewBufferString(`{"name":"Bahama","photoUrls":"http://cat"}`)),
},
wantErrReason: "doesn't match schema #/components/schemas/PetWithRequired",
wantErrSchemaReason: "value must be an array",
wantErrSchemaPath: "/photoUrls",
wantErrSchemaValue: "http://cat",
// TODO: this shouldn't say "or not be present", but this requires recursively resolving
// innerErr.JSONPointer() against e.RequestBody.Content["application/json"].Schema.Value (.Required, .Properties)
wantErrResponse: &ValidationError{
Status: http.StatusUnprocessableEntity,
Title: "value must be an array",
Source: &ValidationErrorSource{Pointer: "/photoUrls"},
},
},
{
name: "error - missing required object attribute from allOf required overlay",
args: validationArgs{
r: newPetstoreRequest(t, http.MethodPost, "/pet2", bytes.NewBufferString(`{"name":"Bahama"}`)),
},
wantErrReason: "doesn't match schema",
wantErrSchemaPath: "/",
wantErrSchemaValue: map[string]string{"name": "Bahama"},
wantErrSchemaReason: `doesn't match all schemas from "allOf"`,
wantErrSchemaOriginReason: `property "photoUrls" is missing`,
wantErrSchemaOriginValue: map[string]string{"name": "Bahama"},
wantErrSchemaOriginPath: "/photoUrls",
wantErrResponse: &ValidationError{
Status: http.StatusUnprocessableEntity,
Title: `property "photoUrls" is missing`,
Source: &ValidationErrorSource{Pointer: "/photoUrls"},
},
},
{
name: "success - ignores unknown object attribute",
args: validationArgs{
r: newPetstoreRequest(t, http.MethodPost, "/pet",
bytes.NewBufferString(`{"wat":"isdis","name":"Bahama","photoUrls":[]}`)),
},
},
{
name: "success - normal case, POST",
args: validationArgs{
r: newPetstoreRequest(t, http.MethodPost, "/pet",
bytes.NewBufferString(`{"name":"Bahama","photoUrls":[]}`)),
},
},
{
name: "success - required properties are not required on PATCH if required overlaid using allOf elsewhere",
args: validationArgs{
r: newPetstoreRequest(t, http.MethodPatch, "/pet", bytes.NewBufferString(`{}`)),
},
},
//
// Path params
//
{
name: "error - missing path param",
args: validationArgs{
r: newPetstoreRequest(t, http.MethodGet, "/pet/", nil),
},
wantErrParam: "petId",
wantErrParamIn: "path",
wantErrBody: `parameter "petId" in path has an error: value is required but missing`,
wantErrReason: "value is required but missing",
wantErrResponse: &ValidationError{
Status: http.StatusBadRequest,
Title: `parameter "petId" in path is required`,
},
},
{
name: "error - wrong path param type",
args: validationArgs{
r: newPetstoreRequest(t, http.MethodGet, "/pet/NotAnInt", nil),
},
wantErrParam: "petId",
wantErrParamIn: "path",
wantErrParseKind: KindInvalidFormat,
wantErrParseValue: "NotAnInt",
wantErrParseReason: "an invalid integer",
wantErrResponse: &ValidationError{
Status: http.StatusNotFound,
Title: `resource not found with "petId" value: NotAnInt`,
},
},
{
name: "success - normal case, with path params",
args: validationArgs{
r: newPetstoreRequest(t, http.MethodGet, "/pet/23", nil),
},
},
}
}
func TestValidationHandler_validateRequest(t *testing.T) {
tests := getValidationTests(t)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := require.New(t)
h, err := buildValidationHandler(tt)
req.NoError(err)
err = h.validateRequest(tt.args.r)
req.Equal(tt.wantErr, err != nil)
if err != nil {
if tt.wantErrBody != "" {
req.Equal(tt.wantErrBody, err.Error())
}
if e, ok := err.(*routers.RouteError); ok {
req.Equal(tt.wantErrReason, e.Error())
return
}
e, ok := err.(*RequestError)
req.True(ok, "not a RequestError: %T -- %#v", err, err)
req.Equal(tt.wantErrReason, e.Reason)
if e.Parameter != nil {
req.Equal(tt.wantErrParam, e.Parameter.Name)
req.Equal(tt.wantErrParamIn, e.Parameter.In)
} else {
req.False(tt.wantErrParam != "" || tt.wantErrParamIn != "",
"error = %v, no Parameter -- %#v", e, e)
}
if innerErr, ok := e.Err.(*openapi3.SchemaError); ok {
req.Equal(tt.wantErrSchemaReason, innerErr.Reason)
pointer := toJSONPointer(innerErr.JSONPointer())
req.Equal(tt.wantErrSchemaPath, pointer)
req.Equal(fmt.Sprintf("%v", tt.wantErrSchemaValue), fmt.Sprintf("%v", innerErr.Value))
if originErr, ok := innerErr.Origin.(*openapi3.SchemaError); ok {
req.Equal(tt.wantErrSchemaOriginReason, originErr.Reason)
pointer := toJSONPointer(originErr.JSONPointer())
req.Equal(tt.wantErrSchemaOriginPath, pointer)
req.Equal(fmt.Sprintf("%v", tt.wantErrSchemaOriginValue), fmt.Sprintf("%v", originErr.Value))
}
} else {
req.False(tt.wantErrSchemaReason != "" || tt.wantErrSchemaPath != "",
"error = %v, not a SchemaError -- %#v", e.Err, e.Err)
req.False(tt.wantErrSchemaOriginReason != "" || tt.wantErrSchemaOriginPath != "",
"error = %v, not a SchemaError with Origin -- %#v", e.Err, e.Err)
}
if innerErr, ok := e.Err.(*ParseError); ok {
req.Equal(tt.wantErrParseKind, innerErr.Kind)
req.Equal(tt.wantErrParseValue, innerErr.Value)
req.Equal(tt.wantErrParseReason, innerErr.Reason)
} else {
req.False(tt.wantErrParseValue != nil || tt.wantErrParseReason != "",
"error = %v, not a ParseError -- %#v", e.Err, e.Err)
}
}
})
}
}
func TestValidationErrorEncoder(t *testing.T) {
tests := getValidationTests(t)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockEncoder := &mockErrorEncoder{}
encoder := &ValidationErrorEncoder{Encoder: mockEncoder.Encode}
req := require.New(t)
h, err := buildValidationHandler(tt)
req.NoError(err)
err = h.validateRequest(tt.args.r)
req.Equal(tt.wantErr, err != nil, "wantError: %v", tt.wantErr)
if err != nil {
encoder.Encode(tt.args.r.Context(), err, httptest.NewRecorder())
if tt.wantErrResponse != mockEncoder.Err {
req.Equal(tt.wantErrResponse, mockEncoder.Err)
}
}
})
}
}
func buildValidationHandler(tt *validationTest) (*ValidationHandler, error) {
if tt.fields.File == "" {
tt.fields.File = "testdata/fixtures/petstore.json"
}
h := &ValidationHandler{
Handler: tt.fields.Handler,
File: tt.fields.File,
ErrorEncoder: tt.fields.ErrorEncoder,
}
tt.wantErr = tt.wantErr ||
(tt.wantErrBody != "") ||
(tt.wantErrReason != "") ||
(tt.wantErrSchemaReason != "") ||
(tt.wantErrSchemaPath != "") ||
(tt.wantErrParseValue != nil) ||
(tt.wantErrParseReason != "")
err := h.Load()
return h, err
}
type testHandler struct {
Called bool
}
func (h *testHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.Called = true
}
type mockErrorEncoder struct {
Called bool
Ctx context.Context
Err error
W http.ResponseWriter
}
func (e *mockErrorEncoder) Encode(ctx context.Context, err error, w http.ResponseWriter) {
e.Called = true
e.Ctx = ctx
e.Err = err
e.W = w
}
func runTest_ServeHTTP(t *testing.T, handler http.Handler, encoder ErrorEncoder, req *http.Request) *http.Response {
h := &ValidationHandler{
Handler: handler,
ErrorEncoder: encoder,
File: "testdata/fixtures/petstore.json",
}
err := h.Load()
require.NoError(t, err)
w := httptest.NewRecorder()
h.ServeHTTP(w, req)
return w.Result()
}
func runTest_Middleware(t *testing.T, handler http.Handler, encoder ErrorEncoder, req *http.Request) *http.Response {
h := &ValidationHandler{
ErrorEncoder: encoder,
File: "testdata/fixtures/petstore.json",
}
err := h.Load()
require.NoError(t, err)
w := httptest.NewRecorder()
h.Middleware(handler).ServeHTTP(w, req)
return w.Result()
}
func TestValidationHandler_ServeHTTP(t *testing.T) {
t.Run("errors on invalid requests", func(t *testing.T) {
httpCtx := context.WithValue(context.Background(), "pig", "tails")
r, err := http.NewRequest(http.MethodGet, "http://unknown-host.com/v2/pet", nil)
require.NoError(t, err)
r = r.WithContext(httpCtx)
handler := &testHandler{}
encoder := &mockErrorEncoder{}
runTest_ServeHTTP(t, handler, encoder.Encode, r)
require.False(t, handler.Called)
require.True(t, encoder.Called)
require.Equal(t, httpCtx, encoder.Ctx)
require.NotNil(t, encoder.Err)
})
t.Run("passes valid requests through", func(t *testing.T) {
r := newPetstoreRequest(t, http.MethodGet, "/pet/findByStatus?status=sold", nil)
handler := &testHandler{}
encoder := &mockErrorEncoder{}
runTest_ServeHTTP(t, handler, encoder.Encode, r)
require.True(t, handler.Called)
require.False(t, encoder.Called)
})
t.Run("uses error encoder", func(t *testing.T) {
r := newPetstoreRequest(t, http.MethodPost, "/pet", bytes.NewBufferString(`{"name":"Bahama","photoUrls":"http://cat"}`))
handler := &testHandler{}
encoder := &ValidationErrorEncoder{Encoder: (ErrorEncoder)(DefaultErrorEncoder)}
resp := runTest_ServeHTTP(t, handler, encoder.Encode, r)
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, http.StatusUnprocessableEntity, resp.StatusCode)
require.Equal(t, "[422][][] value must be an array [source pointer=/photoUrls]", string(body))
})
}
func TestValidationHandler_Middleware(t *testing.T) {
t.Run("errors on invalid requests", func(t *testing.T) {
httpCtx := context.WithValue(context.Background(), "pig", "tails")
r, err := http.NewRequest(http.MethodGet, "http://unknown-host.com/v2/pet", nil)
require.NoError(t, err)
r = r.WithContext(httpCtx)
handler := &testHandler{}
encoder := &mockErrorEncoder{}
runTest_Middleware(t, handler, encoder.Encode, r)
require.False(t, handler.Called)
require.True(t, encoder.Called)
require.Equal(t, httpCtx, encoder.Ctx)
require.NotNil(t, encoder.Err)
})
t.Run("passes valid requests through", func(t *testing.T) {
r := newPetstoreRequest(t, http.MethodGet, "/pet/findByStatus?status=sold", nil)
handler := &testHandler{}
encoder := &mockErrorEncoder{}
runTest_Middleware(t, handler, encoder.Encode, r)
require.True(t, handler.Called)
require.False(t, encoder.Called)
})
t.Run("uses error encoder", func(t *testing.T) {
r := newPetstoreRequest(t, http.MethodPost, "/pet", bytes.NewBufferString(`{"name":"Bahama","photoUrls":"http://cat"}`))
handler := &testHandler{}
encoder := &ValidationErrorEncoder{Encoder: (ErrorEncoder)(DefaultErrorEncoder)}
resp := runTest_Middleware(t, handler, encoder.Encode, r)
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, http.StatusUnprocessableEntity, resp.StatusCode)
require.Equal(t, "[422][][] value must be an array [source pointer=/photoUrls]", string(body))
})
}
kin-openapi-0.124.0/openapi3filter/validation_handler.go 0000664 0000000 0000000 00000005151 14604223742 0023200 0 ustar 00root root 0000000 0000000 package openapi3filter
import (
"context"
"net/http"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/routers"
legacyrouter "github.com/getkin/kin-openapi/routers/legacy"
)
// AuthenticationFunc allows for custom security requirement validation.
// A non-nil error fails authentication according to https://spec.openapis.org/oas/v3.1.0#security-requirement-object
// See ValidateSecurityRequirements
type AuthenticationFunc func(context.Context, *AuthenticationInput) error
// NoopAuthenticationFunc is an AuthenticationFunc
func NoopAuthenticationFunc(context.Context, *AuthenticationInput) error { return nil }
var _ AuthenticationFunc = NoopAuthenticationFunc
type ValidationHandler struct {
Handler http.Handler
AuthenticationFunc AuthenticationFunc
File string
ErrorEncoder ErrorEncoder
router routers.Router
}
func (h *ValidationHandler) Load() error {
loader := openapi3.NewLoader()
doc, err := loader.LoadFromFile(h.File)
if err != nil {
return err
}
if err := doc.Validate(loader.Context); err != nil {
return err
}
if h.router, err = legacyrouter.NewRouter(doc); err != nil {
return err
}
// set defaults
if h.Handler == nil {
h.Handler = http.DefaultServeMux
}
if h.AuthenticationFunc == nil {
h.AuthenticationFunc = NoopAuthenticationFunc
}
if h.ErrorEncoder == nil {
h.ErrorEncoder = DefaultErrorEncoder
}
return nil
}
func (h *ValidationHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if handled := h.before(w, r); handled {
return
}
// TODO: validateResponse
h.Handler.ServeHTTP(w, r)
}
// Middleware implements gorilla/mux MiddlewareFunc
func (h *ValidationHandler) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if handled := h.before(w, r); handled {
return
}
// TODO: validateResponse
next.ServeHTTP(w, r)
})
}
func (h *ValidationHandler) before(w http.ResponseWriter, r *http.Request) (handled bool) {
if err := h.validateRequest(r); err != nil {
h.ErrorEncoder(r.Context(), err, w)
return true
}
return false
}
func (h *ValidationHandler) validateRequest(r *http.Request) error {
// Find route
route, pathParams, err := h.router.FindRoute(r)
if err != nil {
return err
}
options := &Options{
AuthenticationFunc: h.AuthenticationFunc,
}
// Validate request
requestValidationInput := &RequestValidationInput{
Request: r,
PathParams: pathParams,
Route: route,
Options: options,
}
if err = ValidateRequest(r.Context(), requestValidationInput); err != nil {
return err
}
return nil
}
kin-openapi-0.124.0/openapi3filter/validation_kit.go 0000664 0000000 0000000 00000007040 14604223742 0022351 0 ustar 00root root 0000000 0000000 package openapi3filter
import (
"context"
"encoding/json"
"net/http"
)
///////////////////////////////////////////////////////////////////////////////////
// We didn't want to tie kin-openapi too tightly with go-kit.
// This file contains the ErrorEncoder and DefaultErrorEncoder function
// borrowed from this project.
//
// The MIT License (MIT)
//
// Copyright (c) 2015 Peter Bourgon
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
///////////////////////////////////////////////////////////////////////////////////
// ErrorEncoder is responsible for encoding an error to the ResponseWriter.
// Users are encouraged to use custom ErrorEncoders to encode HTTP errors to
// their clients, and will likely want to pass and check for their own error
// types. See the example shipping/handling service.
type ErrorEncoder func(ctx context.Context, err error, w http.ResponseWriter)
// StatusCoder is checked by DefaultErrorEncoder. If an error value implements
// StatusCoder, the StatusCode will be used when encoding the error. By default,
// StatusInternalServerError (500) is used.
type StatusCoder interface {
StatusCode() int
}
// Headerer is checked by DefaultErrorEncoder. If an error value implements
// Headerer, the provided headers will be applied to the response writer, after
// the Content-Type is set.
type Headerer interface {
Headers() http.Header
}
// DefaultErrorEncoder writes the error to the ResponseWriter, by default a
// content type of text/plain, a body of the plain text of the error, and a
// status code of 500. If the error implements Headerer, the provided headers
// will be applied to the response. If the error implements json.Marshaler, and
// the marshaling succeeds, a content type of application/json and the JSON
// encoded form of the error will be used. If the error implements StatusCoder,
// the provided StatusCode will be used instead of 500.
func DefaultErrorEncoder(_ context.Context, err error, w http.ResponseWriter) {
contentType, body := "text/plain; charset=utf-8", []byte(err.Error())
if marshaler, ok := err.(json.Marshaler); ok {
if jsonBody, marshalErr := marshaler.MarshalJSON(); marshalErr == nil {
contentType, body = "application/json; charset=utf-8", jsonBody
}
}
w.Header().Set("Content-Type", contentType)
if headerer, ok := err.(Headerer); ok {
for k, values := range headerer.Headers() {
for _, v := range values {
w.Header().Add(k, v)
}
}
}
code := http.StatusInternalServerError
if sc, ok := err.(StatusCoder); ok {
code = sc.StatusCode()
}
w.WriteHeader(code)
w.Write(body)
}
kin-openapi-0.124.0/openapi3filter/validation_test.go 0000664 0000000 0000000 00000053053 14604223742 0022546 0 ustar 00root root 0000000 0000000 package openapi3filter
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/url"
"reflect"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
legacyrouter "github.com/getkin/kin-openapi/routers/legacy"
)
type ExampleRequest struct {
Method string
URL string
ContentType string
Body interface{}
}
type ExampleResponse struct {
Status int
ContentType string
Body interface{}
}
type ExampleSecurityScheme struct {
Name string
Scheme *openapi3.SecurityScheme
}
func TestFilter(t *testing.T) {
// Declare a schema for an object with name and id properties
complexArgSchema := openapi3.NewObjectSchema().
WithProperty("name", openapi3.NewStringSchema()).
WithProperty("id", openapi3.NewStringSchema().WithMaxLength(2))
complexArgSchema.Required = []string{"name", "id"}
// Declare router
doc := &openapi3.T{
OpenAPI: "3.0.0",
Info: &openapi3.Info{
Title: "MyAPI",
Version: "0.1",
},
Servers: openapi3.Servers{
{
URL: "http://example.com/api/",
},
},
Paths: openapi3.NewPaths(
openapi3.WithPath("/prefix/{pathArg}/suffix", &openapi3.PathItem{
Post: &openapi3.Operation{
Parameters: openapi3.Parameters{
{
Value: &openapi3.Parameter{
In: "path",
Name: "pathArg",
Schema: openapi3.NewStringSchema().WithMaxLength(2).NewRef(),
Required: true,
},
},
{
Value: &openapi3.Parameter{
In: "query",
Name: "queryArg",
Schema: openapi3.NewStringSchema().WithMaxLength(2).NewRef(),
},
},
{
Value: &openapi3.Parameter{
In: "query",
Name: "queryArgAnyOf",
Schema: openapi3.NewAnyOfSchema(
openapi3.NewStringSchema().WithMaxLength(2),
openapi3.NewDateTimeSchema(),
).NewRef(),
},
},
{
Value: &openapi3.Parameter{
In: "query",
Name: "queryArgOneOf",
Schema: openapi3.NewOneOfSchema(
openapi3.NewStringSchema().WithMaxLength(2),
openapi3.NewInt32Schema(),
).NewRef(),
},
},
{
Value: &openapi3.Parameter{
In: "query",
Name: "queryArgAllOf",
Schema: openapi3.NewAllOfSchema(
openapi3.NewDateTimeSchema(),
openapi3.NewStringSchema(),
).NewRef(),
},
},
// TODO(decode not): handle decoding "not" Schema
// {
// Value: &openapi3.Parameter{
// In: "query",
// Name: "queryArgNot",
// Schema: &openapi3.SchemaRef{
// Value: &openapi3.Schema{
// Not: &openapi3.SchemaRef{
// Value: openapi3.NewInt32Schema(),
// }}},
// },
// },
{
Value: &openapi3.Parameter{
In: "query",
Name: "contentArg",
Content: openapi3.NewContentWithJSONSchema(complexArgSchema),
},
},
{
Value: &openapi3.Parameter{
In: "query",
Name: "contentArg2",
Content: openapi3.Content{
"application/something_funny": openapi3.NewMediaType().WithSchema(complexArgSchema),
},
},
},
},
Responses: openapi3.NewResponses(),
},
}),
openapi3.WithPath("/issue151", &openapi3.PathItem{
Get: &openapi3.Operation{
Responses: openapi3.NewResponses(),
},
Parameters: openapi3.Parameters{
{
Value: &openapi3.Parameter{
In: "query",
Name: "par1",
Required: true,
Schema: openapi3.NewIntegerSchema().NewRef(),
},
},
},
}),
),
}
err := doc.Validate(context.Background())
require.NoError(t, err)
router, err := legacyrouter.NewRouter(doc)
require.NoError(t, err)
expectWithDecoder := func(req ExampleRequest, resp ExampleResponse, decoder ContentParameterDecoder) error {
t.Logf("Request: %s %s", req.Method, req.URL)
httpReq, err := http.NewRequest(req.Method, req.URL, marshalReader(req.Body))
require.NoError(t, err)
httpReq.Header.Set(headerCT, req.ContentType)
// Find route
route, pathParams, err := router.FindRoute(httpReq)
require.NoError(t, err)
// Validate request
requestValidationInput := &RequestValidationInput{
Request: httpReq,
PathParams: pathParams,
Route: route,
ParamDecoder: decoder,
}
if err := ValidateRequest(context.Background(), requestValidationInput); err != nil {
return err
}
t.Logf("Response: %d", resp.Status)
responseValidationInput := &ResponseValidationInput{
RequestValidationInput: requestValidationInput,
Status: resp.Status,
Header: http.Header{
headerCT: []string{
resp.ContentType,
},
},
}
if resp.Body != nil {
data, err := json.Marshal(resp.Body)
require.NoError(t, err)
responseValidationInput.SetBodyBytes(data)
}
err = ValidateResponse(context.Background(), responseValidationInput)
require.NoError(t, err)
return nil
}
expect := func(req ExampleRequest, resp ExampleResponse) error {
return expectWithDecoder(req, resp, nil)
}
resp := ExampleResponse{
Status: 200,
}
// Test paths
req := ExampleRequest{
Method: "POST",
URL: "http://example.com/api/prefix/v/suffix",
}
err = expect(req, resp)
require.NoError(t, err)
// Test query parameter openapi3filter
req = ExampleRequest{
Method: "POST",
URL: "http://example.com/api/prefix/EXCEEDS_MAX_LENGTH/suffix",
}
err = expect(req, resp)
require.IsType(t, &RequestError{}, err)
// Test query parameter openapi3filter
req = ExampleRequest{
Method: "POST",
URL: "http://example.com/api/prefix/v/suffix?queryArg=a",
}
err = expect(req, resp)
require.NoError(t, err)
req = ExampleRequest{
Method: "POST",
URL: "http://example.com/api/prefix/v/suffix?queryArg=EXCEEDS_MAX_LENGTH",
}
err = expect(req, resp)
require.IsType(t, &RequestError{}, err)
req = ExampleRequest{
Method: "GET",
URL: "http://example.com/api/issue151?par2=par1_is_missing",
}
err = expect(req, resp)
require.IsType(t, &RequestError{}, err)
// Test query parameter openapi3filter
req = ExampleRequest{
Method: "POST",
URL: "http://example.com/api/prefix/v/suffix?queryArgAnyOf=ae&queryArgOneOf=ac&queryArgAllOf=2017-12-31T11:59:59",
}
err = expect(req, resp)
require.NoError(t, err)
req = ExampleRequest{
Method: "POST",
URL: "http://example.com/api/prefix/v/suffix?queryArgAnyOf=2017-12-31T11:59:59",
}
err = expect(req, resp)
require.NoError(t, err)
req = ExampleRequest{
Method: "POST",
URL: "http://example.com/api/prefix/v/suffix?queryArgAnyOf=123",
}
err = expect(req, resp)
require.IsType(t, &RequestError{}, err)
req = ExampleRequest{
Method: "POST",
URL: "http://example.com/api/prefix/v/suffix?queryArgOneOf=2017-12-31T11:59:59",
}
err = expect(req, resp)
require.IsType(t, &RequestError{}, err)
req = ExampleRequest{
Method: "POST",
URL: "http://example.com/api/prefix/v/suffix?queryArgAllOf=abdfg",
}
err = expect(req, resp)
require.IsType(t, &RequestError{}, err)
// TODO(decode not): handle decoding "not" Schema
// req = ExampleRequest{
// Method: "POST",
// URL: "http://example.com/api/prefix/v/suffix?queryArgNot=abdfg",
// }
// err = expect(req, resp)
// require.IsType(t, &RequestError{}, err)
// TODO(decode not): handle decoding "not" Schema
// req = ExampleRequest{
// Method: "POST",
// URL: "http://example.com/api/prefix/v/suffix?queryArgNot=123",
// }
// err = expect(req, resp)
// require.IsType(t, &RequestError{}, err)
req = ExampleRequest{
Method: "POST",
URL: "http://example.com/api/prefix/v/suffix?queryArg=EXCEEDS_MAX_LENGTH",
}
err = expect(req, resp)
require.IsType(t, &RequestError{}, err)
req = ExampleRequest{
Method: "POST",
URL: "http://example.com/api/prefix/v/suffix",
}
resp = ExampleResponse{
Status: 200,
}
err = expect(req, resp)
// require.IsType(t, &ResponseError{}, err)
require.NoError(t, err)
// Check that content validation works. This should pass, as ID is short
// enough.
req = ExampleRequest{
Method: "POST",
URL: `http://example.com/api/prefix/v/suffix?contentArg={"name":"bob", "id":"a"}`,
}
err = expect(req, resp)
require.NoError(t, err)
// Now it should fail due the ID being too long
req = ExampleRequest{
Method: "POST",
URL: `http://example.com/api/prefix/v/suffix?contentArg={"name":"bob", "id":"EXCEEDS_MAX_LENGTH"}`,
}
err = expect(req, resp)
require.IsType(t, &RequestError{}, err)
// Now, repeat the above two test cases using a custom parameter decoder.
customDecoder := func(param *openapi3.Parameter, values []string) (interface{}, *openapi3.Schema, error) {
var value interface{}
err := json.Unmarshal([]byte(values[0]), &value)
schema := param.Content.Get("application/something_funny").Schema.Value
return value, schema, err
}
req = ExampleRequest{
Method: "POST",
URL: `http://example.com/api/prefix/v/suffix?contentArg2={"name":"bob", "id":"a"}`,
}
err = expectWithDecoder(req, resp, customDecoder)
require.NoError(t, err)
// Now it should fail due the ID being too long
req = ExampleRequest{
Method: "POST",
URL: `http://example.com/api/prefix/v/suffix?contentArg2={"name":"bob", "id":"EXCEEDS_MAX_LENGTH"}`,
}
err = expectWithDecoder(req, resp, customDecoder)
require.IsType(t, &RequestError{}, err)
}
func marshalReader(value interface{}) io.ReadCloser {
if value == nil {
return nil
}
data, err := json.Marshal(value)
if err != nil {
panic(err)
}
return io.NopCloser(bytes.NewReader(data))
}
func TestValidateRequestBody(t *testing.T) {
requiredReqBody := openapi3.NewRequestBody().
WithContent(openapi3.NewContentWithJSONSchema(openapi3.NewStringSchema())).
WithRequired(true)
plainTextContent := openapi3.NewContent()
plainTextContent["text/plain"] = openapi3.NewMediaType().WithSchema(openapi3.NewStringSchema())
testCases := []struct {
name string
body *openapi3.RequestBody
mime string
data io.Reader
wantErr error
}{
{
name: "non required empty",
body: openapi3.NewRequestBody().
WithContent(openapi3.NewContentWithJSONSchema(openapi3.NewStringSchema())),
},
{
name: "non required not empty",
body: openapi3.NewRequestBody().
WithContent(openapi3.NewContentWithJSONSchema(openapi3.NewStringSchema())),
mime: "application/json",
data: toJSON("foo"),
},
{
name: "required empty",
body: requiredReqBody,
wantErr: &RequestError{RequestBody: requiredReqBody, Err: ErrInvalidRequired},
},
{
name: "required not empty",
body: requiredReqBody,
mime: "application/json",
data: toJSON("foo"),
},
{
name: "not JSON data",
body: openapi3.NewRequestBody().WithContent(plainTextContent).WithRequired(true),
mime: "text/plain",
data: strings.NewReader("foo"),
},
{
name: "not declared content",
body: openapi3.NewRequestBody().WithRequired(true),
mime: "application/json",
data: toJSON("foo"),
},
{
name: "not declared schema",
body: openapi3.NewRequestBody().WithJSONSchemaRef(nil),
mime: "application/json",
data: toJSON("foo"),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/test", tc.data)
if tc.mime != "" {
req.Header.Set(headerCT, tc.mime)
}
inp := &RequestValidationInput{Request: req}
err := ValidateRequestBody(context.Background(), inp, tc.body)
if tc.wantErr == nil {
require.NoError(t, err)
return
}
require.True(t, matchReqBodyError(tc.wantErr, err), "got error:\n%s\nwant error\n%s", err, tc.wantErr)
})
}
}
func matchReqBodyError(want, got error) bool {
if want == got {
return true
}
wErr, ok := want.(*RequestError)
if !ok {
return false
}
gErr, ok := got.(*RequestError)
if !ok {
return false
}
if !reflect.DeepEqual(wErr.RequestBody, gErr.RequestBody) {
return false
}
if wErr.Err != nil {
return matchReqBodyError(wErr.Err, gErr.Err)
}
return false
}
func toJSON(v interface{}) io.Reader {
data, err := json.Marshal(v)
if err != nil {
panic(err)
}
return bytes.NewReader(data)
}
func TestRootSecurityRequirementsAreUsedIfNotProvidedAtTheOperationLevel(t *testing.T) {
// Create the security schemes
securitySchemes := []ExampleSecurityScheme{
{
Name: "apikey",
Scheme: &openapi3.SecurityScheme{
Type: "apiKey",
Name: "apikey",
In: "cookie",
},
},
{
Name: "http-basic",
Scheme: &openapi3.SecurityScheme{
Type: "http",
Scheme: "basic",
},
},
}
// Create the test cases
tc := []struct {
name string
schemes *[]ExampleSecurityScheme
expectedSchemes []ExampleSecurityScheme
}{
{
name: "/inherited-security",
schemes: nil,
expectedSchemes: []ExampleSecurityScheme{
securitySchemes[1],
},
},
{
name: "/overwrite-without-security",
schemes: &[]ExampleSecurityScheme{},
expectedSchemes: []ExampleSecurityScheme{},
},
{
name: "/overwrite-with-security",
schemes: &[]ExampleSecurityScheme{
securitySchemes[0],
},
expectedSchemes: []ExampleSecurityScheme{
securitySchemes[0],
},
},
}
doc := &openapi3.T{
OpenAPI: "3.0.0",
Info: &openapi3.Info{
Title: "MyAPI",
Version: "0.1",
},
Paths: openapi3.NewPaths(),
Security: openapi3.SecurityRequirements{
{
securitySchemes[1].Name: {},
},
},
Components: &openapi3.Components{
SecuritySchemes: map[string]*openapi3.SecuritySchemeRef{},
},
}
// Add the security schemes to the components
for _, scheme := range securitySchemes {
doc.Components.SecuritySchemes[scheme.Name] = &openapi3.SecuritySchemeRef{
Value: scheme.Scheme,
}
}
// Add the paths from the test cases to the spec's paths
for _, tc := range tc {
var securityRequirements *openapi3.SecurityRequirements
if tc.schemes != nil {
tempS := openapi3.NewSecurityRequirements()
for _, scheme := range *tc.schemes {
tempS.With(openapi3.SecurityRequirement{scheme.Name: {}})
}
securityRequirements = tempS
}
doc.Paths.Set(tc.name, &openapi3.PathItem{
Get: &openapi3.Operation{
Security: securityRequirements,
Responses: openapi3.NewResponses(),
},
})
}
err := doc.Validate(context.Background())
require.NoError(t, err)
router, err := legacyrouter.NewRouter(doc)
require.NoError(t, err)
// Test each case
for _, path := range tc {
// Make a map of the schemes and whether they're
schemesValidated := make(map[*openapi3.SecurityScheme]bool)
for _, scheme := range path.expectedSchemes {
schemesValidated[scheme.Scheme] = false
}
// Create the request
emptyBody := bytes.NewReader(make([]byte, 0))
httpReq := httptest.NewRequest(http.MethodGet, path.name, emptyBody)
route, _, err := router.FindRoute(httpReq)
require.NoError(t, err)
req := RequestValidationInput{
Request: httpReq,
Route: route,
Options: &Options{
AuthenticationFunc: func(ctx context.Context, input *AuthenticationInput) error {
if validated, ok := schemesValidated[input.SecurityScheme]; ok {
if validated {
t.Fatalf("The path %q had the schemes %v named %q validated more than once",
path.name, input.SecurityScheme, input.SecuritySchemeName)
}
schemesValidated[input.SecurityScheme] = true
return nil
}
t.Fatalf("The path %q had the schemes %v named %q",
path.name, input.SecurityScheme, input.SecuritySchemeName)
return nil
},
},
}
// Validate the request
err = ValidateRequest(context.Background(), &req)
require.NoError(t, err)
for securityRequirement, validated := range schemesValidated {
if !validated {
t.Fatalf("The security requirement %v was unexpected to be validated but wasn't",
securityRequirement)
}
}
}
}
// TestAlternateRequirementMet asserts that ValidateSecurityRequirements succeeds if any SecurityRequirement is met and otherwise doesn't.
func TestAnySecurityRequirementMet(t *testing.T) {
// Create of a map of scheme names and whether they are valid
schemes := map[string]bool{
"a": true,
"b": true,
"c": false,
"d": false,
}
// Create the test cases
tc := []struct {
name string
schemes []string
error bool
}{
{
name: "/ok1",
schemes: []string{"a", "b"},
error: false,
},
{
name: "/ok2",
schemes: []string{"a", "c"},
error: false,
},
{
name: "/error",
schemes: []string{"c", "d"},
error: true,
},
}
doc := openapi3.T{
OpenAPI: "3.0.0",
Info: &openapi3.Info{
Title: "MyAPI",
Version: "0.1",
},
Paths: openapi3.NewPaths(),
Components: &openapi3.Components{
SecuritySchemes: map[string]*openapi3.SecuritySchemeRef{},
},
}
// Add the security schemes to the spec's components
for schemeName := range schemes {
doc.Components.SecuritySchemes[schemeName] = &openapi3.SecuritySchemeRef{
Value: &openapi3.SecurityScheme{
Type: "http",
Scheme: "basic",
},
}
}
// Add the paths to the spec
for _, tc := range tc {
// Create the security requirements from the test cases's schemes
securityRequirements := openapi3.NewSecurityRequirements()
for _, scheme := range tc.schemes {
securityRequirements.With(openapi3.SecurityRequirement{scheme: {}})
}
// Create the path with the security requirements
doc.Paths.Set(tc.name, &openapi3.PathItem{
Get: &openapi3.Operation{
Security: securityRequirements,
Responses: openapi3.NewResponses(),
},
})
}
err := doc.Validate(context.Background())
require.NoError(t, err)
router, err := legacyrouter.NewRouter(&doc)
require.NoError(t, err)
// Create the authentication function
authFunc := makeAuthFunc(schemes)
for _, tc := range tc {
// Create the request input for the path
tcURL, err := url.Parse(tc.name)
require.NoError(t, err)
httpReq := httptest.NewRequest(http.MethodGet, tcURL.String(), nil)
route, _, err := router.FindRoute(httpReq)
require.NoError(t, err)
req := RequestValidationInput{
Route: route,
Options: &Options{
AuthenticationFunc: authFunc,
},
}
// Validate the security requirements
err = ValidateSecurityRequirements(context.Background(), &req, *route.Operation.Security)
// If there should have been an error
if tc.error {
require.Errorf(t, err, "an error is expected for path %q", tc.name)
} else {
require.NoErrorf(t, err, "an error wasn't expected for path %q", tc.name)
}
}
}
// TestAllSchemesMet asserts that ValidateSecurityRequirement succeeds if all the SecuritySchemes of a SecurityRequirement are met and otherwise doesn't.
func TestAllSchemesMet(t *testing.T) {
// Create of a map of scheme names and whether they are met
schemes := map[string]bool{
"a": true,
"b": true,
"c": false,
}
// Create the test cases
tc := []struct {
name string
error bool
}{
{
name: "/ok",
error: false,
},
{
name: "/error",
error: true,
},
}
doc := openapi3.T{
OpenAPI: "3.0.0",
Info: &openapi3.Info{
Title: "MyAPI",
Version: "0.1",
},
Paths: openapi3.NewPaths(),
Components: &openapi3.Components{
SecuritySchemes: map[string]*openapi3.SecuritySchemeRef{},
},
}
// Add the security schemes to the spec's components
for schemeName := range schemes {
doc.Components.SecuritySchemes[schemeName] = &openapi3.SecuritySchemeRef{
Value: &openapi3.SecurityScheme{
Type: "http",
Scheme: "basic",
},
}
}
// Add the paths to the spec
for _, tc := range tc {
// Create the security requirement for the path
securityRequirement := openapi3.SecurityRequirement{}
for scheme, valid := range schemes {
// If the scheme is valid or the test case is meant to return an error
if valid || tc.error {
// Add the scheme to the security requirement
securityRequirement[scheme] = []string{}
}
}
doc.Paths.Set(tc.name, &openapi3.PathItem{
Get: &openapi3.Operation{
Security: &openapi3.SecurityRequirements{
securityRequirement,
},
Responses: openapi3.NewResponses(),
},
})
}
err := doc.Validate(context.Background())
require.NoError(t, err)
router, err := legacyrouter.NewRouter(&doc)
require.NoError(t, err)
// Create the authentication function
authFunc := makeAuthFunc(schemes)
for _, tc := range tc {
// Create the request input for the path
tcURL, err := url.Parse(tc.name)
require.NoError(t, err)
httpReq := httptest.NewRequest(http.MethodGet, tcURL.String(), nil)
route, _, err := router.FindRoute(httpReq)
require.NoError(t, err)
req := RequestValidationInput{
Route: route,
Options: &Options{
AuthenticationFunc: authFunc,
},
}
// Validate the security requirements
err = ValidateSecurityRequirements(context.Background(), &req, *route.Operation.Security)
// If there should have been an error
if tc.error {
require.Error(t, err)
} else {
require.NoError(t, err)
}
}
}
// makeAuthFunc creates an authentication function that accepts the given valid schemes.
// If an invalid or unknown scheme is encountered, an error is returned by the returned function.
// Otherwise the return value of the returned function is nil.
func makeAuthFunc(schemes map[string]bool) func(ctx context.Context, input *AuthenticationInput) error {
return func(ctx context.Context, input *AuthenticationInput) error {
// If the scheme is valid and present in the schemes
valid, present := schemes[input.SecuritySchemeName]
if valid && present {
return nil
}
// If the scheme is present in che schemes
if present {
// Return an unmet scheme error
return fmt.Errorf("security scheme for %q wasn't met", input.SecuritySchemeName)
}
// Return an unknown scheme error
return fmt.Errorf("security scheme for %q is unknown", input.SecuritySchemeName)
}
}
kin-openapi-0.124.0/openapi3filter/zip_file_upload_test.go 0000664 0000000 0000000 00000006526 14604223742 0023564 0 ustar 00root root 0000000 0000000 package openapi3filter_test
import (
"bytes"
"context"
"io"
"mime/multipart"
"net/http"
"net/textproto"
"testing"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/openapi3filter"
"github.com/getkin/kin-openapi/routers/gorillamux"
)
func TestValidateZipFileUpload(t *testing.T) {
const spec = `
openapi: 3.0.0
info:
title: 'Validator'
version: 0.0.1
paths:
/test:
post:
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required:
- file
properties:
file:
type: string
format: binary
responses:
'200':
description: Created
`
loader := openapi3.NewLoader()
doc, err := loader.LoadFromData([]byte(spec))
require.NoError(t, err)
err = doc.Validate(loader.Context)
require.NoError(t, err)
router, err := gorillamux.NewRouter(doc)
require.NoError(t, err)
tests := []struct {
zipData []byte
wantErr bool
}{
{
[]byte{
0x50, 0x4b, 0x03, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x7d, 0x23, 0x56, 0xcd, 0xfd, 0x67, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x09, 0x00, 0x1c, 0x00, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x74, 0x78, 0x74, 0x55, 0x54, 0x09, 0x00, 0x03, 0xac, 0xce, 0xb3, 0x63, 0xaf, 0xce, 0xb3, 0x63, 0x75, 0x78, 0x0b, 0x00, 0x01, 0x04, 0xf7, 0x01, 0x00, 0x00, 0x04, 0x14, 0x00, 0x00, 0x00, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x0a, 0x50, 0x4b, 0x01, 0x02, 0x1e, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x7d, 0x23, 0x56, 0xcd, 0xfd, 0x67, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x09, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x00, 0x00, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x74, 0x78, 0x74, 0x55, 0x54, 0x05, 0x00, 0x03, 0xac, 0xce, 0xb3, 0x63, 0x75, 0x78, 0x0b, 0x00, 0x01, 0x04, 0xf7, 0x01, 0x00, 0x00, 0x04, 0x14, 0x00, 0x00, 0x00, 0x50, 0x4b, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00, 0x00,
},
false,
},
{
[]byte{
0x50, 0x4b, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}, // No entry
true,
},
}
for _, tt := range tests {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
{ // Add file data
h := make(textproto.MIMEHeader)
h.Set("Content-Disposition", `form-data; name="file"; filename="hello.zip"`)
h.Set("Content-Type", "application/zip")
fw, err := writer.CreatePart(h)
require.NoError(t, err)
_, err = io.Copy(fw, bytes.NewReader(tt.zipData))
require.NoError(t, err)
}
writer.Close()
req, err := http.NewRequest(http.MethodPost, "/test", bytes.NewReader(body.Bytes()))
require.NoError(t, err)
req.Header.Set("Content-Type", writer.FormDataContentType())
route, pathParams, err := router.FindRoute(req)
require.NoError(t, err)
if err = openapi3filter.ValidateRequestBody(
context.Background(),
&openapi3filter.RequestValidationInput{
Request: req,
PathParams: pathParams,
Route: route,
},
route.Operation.RequestBody.Value,
); err != nil {
if !tt.wantErr {
t.Errorf("got %v", err)
}
continue
}
if tt.wantErr {
t.Errorf("want err")
}
}
}
kin-openapi-0.124.0/openapi3gen/ 0000775 0000000 0000000 00000000000 14604223742 0016304 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3gen/field_info.go 0000664 0000000 0000000 00000004542 14604223742 0020736 0 ustar 00root root 0000000 0000000 package openapi3gen
import (
"reflect"
"strings"
"unicode"
"unicode/utf8"
)
// theFieldInfo contains information about JSON serialization of a field.
type theFieldInfo struct {
HasJSONTag bool
TypeIsMarshaler bool
TypeIsUnmarshaler bool
JSONOmitEmpty bool
JSONString bool
Index []int
Type reflect.Type
JSONName string
}
func appendFields(fields []theFieldInfo, parentIndex []int, t reflect.Type) []theFieldInfo {
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Kind() != reflect.Struct {
return fields
}
// For each field
numField := t.NumField()
iteration:
for i := 0; i < numField; i++ {
f := t.Field(i)
index := make([]int, 0, len(parentIndex)+1)
index = append(index, parentIndex...)
index = append(index, i)
// See whether this is an embedded field
if f.Anonymous {
jsonTag := f.Tag.Get("json")
if jsonTag == "-" {
continue
}
if jsonTag == "" {
fields = appendFields(fields, index, f.Type)
continue iteration
}
}
// Ignore certain types
switch f.Type.Kind() {
case reflect.Func, reflect.Chan:
continue iteration
}
// Is it a private (lowercase) field?
firstRune, _ := utf8.DecodeRuneInString(f.Name)
if unicode.IsLower(firstRune) {
continue iteration
}
// Declare a field
field := theFieldInfo{
Index: index,
Type: f.Type,
JSONName: f.Name,
}
// Read "json" tag
jsonTag := f.Tag.Get("json")
// Handle "-"
if jsonTag == "-" {
continue
}
// Parse the tag
if jsonTag != "" {
field.HasJSONTag = true
for i, part := range strings.Split(jsonTag, ",") {
if i == 0 {
if part != "" {
field.JSONName = part
}
} else {
switch part {
case "omitempty":
field.JSONOmitEmpty = true
case "string":
field.JSONString = true
}
}
}
}
_, field.TypeIsMarshaler = field.Type.MethodByName("MarshalJSON")
_, field.TypeIsUnmarshaler = field.Type.MethodByName("UnmarshalJSON")
// Field is done
fields = append(fields, field)
}
return fields
}
type sortableFieldInfos []theFieldInfo
func (list sortableFieldInfos) Len() int {
return len(list)
}
func (list sortableFieldInfos) Less(i, j int) bool {
return list[i].JSONName < list[j].JSONName
}
func (list sortableFieldInfos) Swap(i, j int) {
a, b := list[i], list[j]
list[i], list[j] = b, a
}
kin-openapi-0.124.0/openapi3gen/internal/ 0000775 0000000 0000000 00000000000 14604223742 0020120 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3gen/internal/subpkg/ 0000775 0000000 0000000 00000000000 14604223742 0021413 5 ustar 00root root 0000000 0000000 kin-openapi-0.124.0/openapi3gen/internal/subpkg/sub_type.go 0000664 0000000 0000000 00000000101 14604223742 0023564 0 ustar 00root root 0000000 0000000 package subpkg
type Child struct {
Name string `yaml:"name"`
}
kin-openapi-0.124.0/openapi3gen/openapi3gen.go 0000664 0000000 0000000 00000034720 14604223742 0021051 0 ustar 00root root 0000000 0000000 // Package openapi3gen generates OpenAPIv3 JSON schemas from Go types.
package openapi3gen
import (
"encoding/json"
"fmt"
"math"
"reflect"
"regexp"
"strings"
"time"
"github.com/getkin/kin-openapi/openapi3"
)
// CycleError indicates that a type graph has one or more possible cycles.
type CycleError struct{}
func (err *CycleError) Error() string { return "detected cycle" }
// ExcludeSchemaSentinel indicates that the schema for a specific field should not be included in the final output.
type ExcludeSchemaSentinel struct{}
func (err *ExcludeSchemaSentinel) Error() string { return "schema excluded" }
// Option allows tweaking SchemaRef generation
type Option func(*generatorOpt)
// SchemaCustomizerFn is a callback function, allowing
// the OpenAPI schema definition to be updated with additional
// properties during the generation process, based on the
// name of the field, the Go type, and the struct tags.
// name will be "_root" for the top level object, and tag will be "".
// A SchemaCustomizerFn can return an ExcludeSchemaSentinel error to
// indicate that the schema for this field should not be included in
// the final output
type SchemaCustomizerFn func(name string, t reflect.Type, tag reflect.StructTag, schema *openapi3.Schema) error
// SetSchemar allows client to set their own schema definition according to
// their specification. Useful when some custom datatype is needed and/or some custom logic
// is needed on how the schema values would be generated
type SetSchemar interface {
SetSchema(*openapi3.Schema)
}
type ExportComponentSchemasOptions struct {
ExportComponentSchemas bool
ExportTopLevelSchema bool
ExportGenerics bool
}
type TypeNameGenerator func(t reflect.Type) string
type generatorOpt struct {
useAllExportedFields bool
throwErrorOnCycle bool
schemaCustomizer SchemaCustomizerFn
exportComponentSchemas ExportComponentSchemasOptions
typeNameGenerator TypeNameGenerator
}
// UseAllExportedFields changes the default behavior of only
// generating schemas for struct fields with a JSON tag.
func UseAllExportedFields() Option {
return func(x *generatorOpt) { x.useAllExportedFields = true }
}
func CreateTypeNameGenerator(tngnrt TypeNameGenerator) Option {
return func(x *generatorOpt) { x.typeNameGenerator = tngnrt }
}
// ThrowErrorOnCycle changes the default behavior of creating cycle
// refs to instead error if a cycle is detected.
func ThrowErrorOnCycle() Option {
return func(x *generatorOpt) { x.throwErrorOnCycle = true }
}
// SchemaCustomizer allows customization of the schema that is generated
// for a field, for example to support an additional tagging scheme
func SchemaCustomizer(sc SchemaCustomizerFn) Option {
return func(x *generatorOpt) { x.schemaCustomizer = sc }
}
// CreateComponents changes the default behavior
// to add all schemas as components
// Reduces duplicate schemas in routes
func CreateComponentSchemas(exso ExportComponentSchemasOptions) Option {
return func(x *generatorOpt) { x.exportComponentSchemas = exso }
}
// NewSchemaRefForValue is a shortcut for NewGenerator(...).NewSchemaRefForValue(...)
func NewSchemaRefForValue(value interface{}, schemas openapi3.Schemas, opts ...Option) (*openapi3.SchemaRef, error) {
g := NewGenerator(opts...)
return g.NewSchemaRefForValue(value, schemas)
}
type Generator struct {
opts generatorOpt
Types map[reflect.Type]*openapi3.SchemaRef
// SchemaRefs contains all references and their counts.
// If count is 1, it's not ne
// An OpenAPI identifier has been assigned to each.
SchemaRefs map[*openapi3.SchemaRef]int
// componentSchemaRefs is a set of schemas that must be defined in the components to avoid cycles
// or if we have specified create components schemas
componentSchemaRefs map[string]struct{}
}
func NewGenerator(opts ...Option) *Generator {
gOpt := &generatorOpt{}
for _, f := range opts {
f(gOpt)
}
return &Generator{
Types: make(map[reflect.Type]*openapi3.SchemaRef),
SchemaRefs: make(map[*openapi3.SchemaRef]int),
componentSchemaRefs: make(map[string]struct{}),
opts: *gOpt,
}
}
func (g *Generator) GenerateSchemaRef(t reflect.Type) (*openapi3.SchemaRef, error) {
//check generatorOpt consistency here
return g.generateSchemaRefFor(nil, t, "_root", "")
}
// NewSchemaRefForValue uses reflection on the given value to produce a SchemaRef, and updates a supplied map with any dependent component schemas if they lead to cycles
func (g *Generator) NewSchemaRefForValue(value interface{}, schemas openapi3.Schemas) (*openapi3.SchemaRef, error) {
ref, err := g.GenerateSchemaRef(reflect.TypeOf(value))
if err != nil {
return nil, err
}
for ref := range g.SchemaRefs {
refName := ref.Ref
if g.opts.exportComponentSchemas.ExportComponentSchemas && strings.HasPrefix(refName, "#/components/schemas/") {
refName = strings.TrimPrefix(refName, "#/components/schemas/")
}
if _, ok := g.componentSchemaRefs[refName]; ok && schemas != nil {
if ref.Value != nil && ref.Value.Properties != nil {
schemas[refName] = &openapi3.SchemaRef{
Value: ref.Value,
}
}
}
if strings.HasPrefix(ref.Ref, "#/components/schemas/") {
ref.Value = nil
} else {
ref.Ref = ""
}
}
return ref, nil
}
func (g *Generator) generateSchemaRefFor(parents []*theTypeInfo, t reflect.Type, name string, tag reflect.StructTag) (*openapi3.SchemaRef, error) {
if ref := g.Types[t]; ref != nil && g.opts.schemaCustomizer == nil {
g.SchemaRefs[ref]++
return ref, nil
}
ref, err := g.generateWithoutSaving(parents, t, name, tag)
if _, ok := err.(*ExcludeSchemaSentinel); ok {
// This schema should not be included in the final output
return nil, nil
}
if err != nil {
return nil, err
}
if ref != nil {
g.Types[t] = ref
g.SchemaRefs[ref]++
}
return ref, nil
}
func getStructField(t reflect.Type, fieldInfo theFieldInfo) reflect.StructField {
var ff reflect.StructField
// fieldInfo.Index is an array of indexes starting from the root of the type
for i := 0; i < len(fieldInfo.Index); i++ {
ff = t.Field(fieldInfo.Index[i])
t = ff.Type
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
}
return ff
}
func (g *Generator) generateWithoutSaving(parents []*theTypeInfo, t reflect.Type, name string, tag reflect.StructTag) (*openapi3.SchemaRef, error) {
typeInfo := getTypeInfo(t)
for _, parent := range parents {
if parent == typeInfo {
return nil, &CycleError{}
}
}
if cap(parents) == 0 {
parents = make([]*theTypeInfo, 0, 4)
}
parents = append(parents, typeInfo)
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
if strings.HasSuffix(t.Name(), "Ref") {
_, a := t.FieldByName("Ref")
v, b := t.FieldByName("Value")
if a && b {
vs, err := g.generateSchemaRefFor(parents, v.Type, name, tag)
if err != nil {
if _, ok := err.(*CycleError); ok && !g.opts.throwErrorOnCycle {
g.SchemaRefs[vs]++
return vs, nil
}
return nil, err
}
refSchemaRef := RefSchemaRef
g.SchemaRefs[refSchemaRef]++
ref := openapi3.NewSchemaRef(t.Name(), &openapi3.Schema{
OneOf: []*openapi3.SchemaRef{
refSchemaRef,
vs,
},
})
g.SchemaRefs[ref]++
return ref, nil
}
}
schema := &openapi3.Schema{}
switch t.Kind() {
case reflect.Func, reflect.Chan:
return nil, nil // ignore
case reflect.Bool:
schema.Type = &openapi3.Types{"boolean"}
case reflect.Int:
schema.Type = &openapi3.Types{"integer"}
case reflect.Int8:
schema.Type = &openapi3.Types{"integer"}
schema.Min = &minInt8
schema.Max = &maxInt8
case reflect.Int16:
schema.Type = &openapi3.Types{"integer"}
schema.Min = &minInt16
schema.Max = &maxInt16
case reflect.Int32:
schema.Type = &openapi3.Types{"integer"}
schema.Format = "int32"
case reflect.Int64:
schema.Type = &openapi3.Types{"integer"}
schema.Format = "int64"
case reflect.Uint:
schema.Type = &openapi3.Types{"integer"}
schema.Min = &zeroInt
case reflect.Uint8:
schema.Type = &openapi3.Types{"integer"}
schema.Min = &zeroInt
schema.Max = &maxUint8
case reflect.Uint16:
schema.Type = &openapi3.Types{"integer"}
schema.Min = &zeroInt
schema.Max = &maxUint16
case reflect.Uint32:
schema.Type = &openapi3.Types{"integer"}
schema.Min = &zeroInt
schema.Max = &maxUint32
case reflect.Uint64:
schema.Type = &openapi3.Types{"integer"}
schema.Min = &zeroInt
schema.Max = &maxUint64
case reflect.Float32:
schema.Type = &openapi3.Types{"number"}
schema.Format = "float"
case reflect.Float64:
schema.Type = &openapi3.Types{"number"}
schema.Format = "double"
case reflect.String:
schema.Type = &openapi3.Types{"string"}
case reflect.Slice:
if t.Elem().Kind() == reflect.Uint8 {
if t == rawMessageType {
return &openapi3.SchemaRef{Value: schema}, nil
}
schema.Type = &openapi3.Types{"string"}
schema.Format = "byte"
} else {
schema.Type = &openapi3.Types{"array"}
items, err := g.generateSchemaRefFor(parents, t.Elem(), name, tag)
if err != nil {
if _, ok := err.(*CycleError); ok && !g.opts.throwErrorOnCycle {
items = g.generateCycleSchemaRef(t.Elem(), schema)
} else {
return nil, err
}
}
if items != nil {
g.SchemaRefs[items]++
schema.Items = items
}
}
case reflect.Map:
schema.Type = &openapi3.Types{"object"}
additionalProperties, err := g.generateSchemaRefFor(parents, t.Elem(), name, tag)
if err != nil {
if _, ok := err.(*CycleError); ok && !g.opts.throwErrorOnCycle {
additionalProperties = g.generateCycleSchemaRef(t.Elem(), schema)
} else {
return nil, err
}
}
if additionalProperties != nil {
g.SchemaRefs[additionalProperties]++
schema.AdditionalProperties = openapi3.AdditionalProperties{Schema: additionalProperties}
}
case reflect.Struct:
if t == timeType {
schema.Type = &openapi3.Types{"string"}
schema.Format = "date-time"
} else {
typeName := g.generateTypeName(t)
if _, ok := g.componentSchemaRefs[typeName]; ok && g.opts.exportComponentSchemas.ExportComponentSchemas {
// Check if we have already parsed this component schema ref based on the name of the struct
// and use that if so
return openapi3.NewSchemaRef(fmt.Sprintf("#/components/schemas/%s", typeName), schema), nil
}
for _, fieldInfo := range typeInfo.Fields {
// Only fields with JSON tag are considered (by default)
if !fieldInfo.HasJSONTag && !g.opts.useAllExportedFields {
continue
}
// If asked, try to use yaml tag
fieldName, fType := fieldInfo.JSONName, fieldInfo.Type
if !fieldInfo.HasJSONTag && g.opts.useAllExportedFields {
// Handle anonymous fields/embedded structs
if t.Field(fieldInfo.Index[0]).Anonymous {
ref, err := g.generateSchemaRefFor(parents, fType, fieldName, tag)
if err != nil {
if _, ok := err.(*CycleError); ok && !g.opts.throwErrorOnCycle {
ref = g.generateCycleSchemaRef(fType, schema)
} else {
return nil, err
}
}
if ref != nil {
g.SchemaRefs[ref]++
schema.WithPropertyRef(fieldName, ref)
}
} else {
ff := getStructField(t, fieldInfo)
if tag, ok := ff.Tag.Lookup("yaml"); ok && tag != "-" {
fieldName, fType = tag, ff.Type
}
}
}
// extract the field tag if we have a customizer
var fieldTag reflect.StructTag
if g.opts.schemaCustomizer != nil {
ff := getStructField(t, fieldInfo)
fieldTag = ff.Tag
}
ref, err := g.generateSchemaRefFor(parents, fType, fieldName, fieldTag)
if err != nil {
if _, ok := err.(*CycleError); ok && !g.opts.throwErrorOnCycle {
ref = g.generateCycleSchemaRef(fType, schema)
} else {
return nil, err
}
}
if ref != nil {
g.SchemaRefs[ref]++
schema.WithPropertyRef(fieldName, ref)
}
}
// Object only if it has properties
if schema.Properties != nil {
schema.Type = &openapi3.Types{"object"}
}
}
default:
// Object has their own schema's implementation, so we'll use those
if v := reflect.New(t); v.CanInterface() {
if v, ok := v.Interface().(SetSchemar); ok {
v.SetSchema(schema)
}
}
}
if g.opts.schemaCustomizer != nil {
if err := g.opts.schemaCustomizer(name, t, tag, schema); err != nil {
return nil, err
}
}
if !g.opts.exportComponentSchemas.ExportComponentSchemas || t.Kind() != reflect.Struct {
return openapi3.NewSchemaRef(t.Name(), schema), nil
}
// Best way I could find to check that
// this current type is a generic
isGeneric, err := regexp.Match(`^.*\[.*\]$`, []byte(t.Name()))
if err != nil {
return nil, err
}
if isGeneric && !g.opts.exportComponentSchemas.ExportGenerics {
return openapi3.NewSchemaRef(t.Name(), schema), nil
}
// For structs we add the schemas to the component schemas
if len(parents) > 1 || g.opts.exportComponentSchemas.ExportTopLevelSchema {
typeName := g.generateTypeName(t)
g.componentSchemaRefs[typeName] = struct{}{}
return openapi3.NewSchemaRef(fmt.Sprintf("#/components/schemas/%s", typeName), schema), nil
}
return openapi3.NewSchemaRef(t.Name(), schema), nil
}
func (g *Generator) generateTypeName(t reflect.Type) string {
if g.opts.typeNameGenerator != nil {
return g.opts.typeNameGenerator(t)
}
return t.Name()
}
func (g *Generator) generateCycleSchemaRef(t reflect.Type, schema *openapi3.Schema) *openapi3.SchemaRef {
var typeName string
switch t.Kind() {
case reflect.Ptr:
return g.generateCycleSchemaRef(t.Elem(), schema)
case reflect.Slice:
ref := g.generateCycleSchemaRef(t.Elem(), schema)
sliceSchema := openapi3.NewSchema()
sliceSchema.Type = &openapi3.Types{"array"}
sliceSchema.Items = ref
return openapi3.NewSchemaRef("", sliceSchema)
case reflect.Map:
ref := g.generateCycleSchemaRef(t.Elem(), schema)
mapSchema := openapi3.NewSchema()
mapSchema.Type = &openapi3.Types{"object"}
mapSchema.AdditionalProperties = openapi3.AdditionalProperties{Schema: ref}
return openapi3.NewSchemaRef("", mapSchema)
default:
typeName = g.generateTypeName(t)
}
g.componentSchemaRefs[typeName] = struct{}{}
return openapi3.NewSchemaRef(fmt.Sprintf("#/components/schemas/%s", typeName), schema)
}
var RefSchemaRef = openapi3.NewSchemaRef("Ref",
openapi3.NewObjectSchema().WithProperty("$ref", openapi3.NewStringSchema().WithMinLength(1)))
var (
timeType = reflect.TypeOf(time.Time{})
rawMessageType = reflect.TypeOf(json.RawMessage{})
zeroInt = float64(0)
maxInt8 = float64(math.MaxInt8)
minInt8 = float64(math.MinInt8)
maxInt16 = float64(math.MaxInt16)
minInt16 = float64(math.MinInt16)
maxUint8 = float64(math.MaxUint8)
maxUint16 = float64(math.MaxUint16)
maxUint32 = float64(math.MaxUint32)
maxUint64 = float64(math.MaxUint64)
)
kin-openapi-0.124.0/openapi3gen/openapi3gen_newschemarefforvalue_test.go 0000664 0000000 0000000 00000024501 14604223742 0026377 0 ustar 00root root 0000000 0000000 package openapi3gen_test
import (
"encoding/json"
"fmt"
"reflect"
"strings"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/openapi3gen"
"github.com/getkin/kin-openapi/openapi3gen/internal/subpkg"
)
// Make sure that custom schema name generator is employed and results produced with it are properly used
func ExampleNewSchemaRefForValue_withSubPackages() {
type Parent struct {
Field1 string `json:"field1"`
Child subpkg.Child `json:"child"`
}
// these schema names should be returned by name generator
parentSchemaName := "PARENT_TYPE"
childSchemaName := "CHILD_TYPE"
// sample of a type name generator
typeNameGenerator := func(t reflect.Type) string {
switch t {
case reflect.TypeOf(Parent{}):
return parentSchemaName
case reflect.TypeOf(subpkg.Child{}):
return childSchemaName
}
return t.Name()
}
schemas := make(openapi3.Schemas)
schemaRef, err := openapi3gen.NewSchemaRefForValue(
&Parent{},
schemas,
openapi3gen.CreateComponentSchemas(openapi3gen.ExportComponentSchemasOptions{
ExportComponentSchemas: true, ExportTopLevelSchema: true,
}),
openapi3gen.CreateTypeNameGenerator(typeNameGenerator),
openapi3gen.UseAllExportedFields(),
)
if err != nil {
panic(err)
}
var data []byte
if data, err = json.MarshalIndent(&schemas, "", " "); err != nil {
panic(err)
}
fmt.Printf("schemas: %s\n", data)
if data, err = json.MarshalIndent(&schemaRef, "", " "); err != nil {
panic(err)
}
fmt.Printf("schemaRef: %s\n", data)
// Output:
// schemas: {
// "CHILD_TYPE": {
// "properties": {
// "name": {
// "type": "string"
// }
// },
// "type": "object"
// },
// "PARENT_TYPE": {
// "properties": {
// "child": {
// "$ref": "#/components/schemas/CHILD_TYPE"
// },
// "field1": {
// "type": "string"
// }
// },
// "type": "object"
// }
// }
// schemaRef: {
// "$ref": "#/components/schemas/PARENT_TYPE"
// }
}
func ExampleNewSchemaRefForValue_withExportingSchemas() {
type Child struct {
Age string `json:"age"`
}
type AnotherStruct struct {
Field1 string `json:"field1"`
Field2 string `json:"field2"`
Field3 string `json:"field3"`
}
type RecursiveType struct {
Field1 string `json:"field1"`
Field2 string `json:"field2"`
Field3 string `json:"field3"`
AnotherStruct AnotherStruct `json:"children,omitempty"`
Child subpkg.Child `json:"child"`
Child2 Child `json:"child2"`
}
// sample of a type name generator
typeNameGenerator := func(t reflect.Type) string {
packages := strings.Split(t.PkgPath(), "/")
return packages[len(packages)-1] + "_" + t.Name()
}
schemas := make(openapi3.Schemas)
schemaRef, err := openapi3gen.NewSchemaRefForValue(
&RecursiveType{},
schemas,
openapi3gen.CreateComponentSchemas(openapi3gen.ExportComponentSchemasOptions{
ExportComponentSchemas: true, ExportTopLevelSchema: false,
}),
openapi3gen.CreateTypeNameGenerator(typeNameGenerator),
openapi3gen.UseAllExportedFields(),
)
if err != nil {
panic(err)
}
var schemasByte []byte
if schemasByte, err = json.MarshalIndent(&schemas, "", " "); err != nil {
panic(err)
}
var schemaRefByte []byte
if schemaRefByte, err = json.MarshalIndent(&schemaRef, "", " "); err != nil {
panic(err)
}
fmt.Printf("schemas: %s\nschemaRef: %s\n", schemasByte, schemaRefByte)
// Output:
// schemas: {
// "openapi3gen_test_AnotherStruct": {
// "properties": {
// "field1": {
// "type": "string"
// },
// "field2": {
// "type": "string"
// },
// "field3": {
// "type": "string"
// }
// },
// "type": "object"
// },
// "openapi3gen_test_Child": {
// "properties": {
// "age": {
// "type": "string"
// }
// },
// "type": "object"
// },
// "subpkg_Child": {
// "properties": {
// "name": {
// "type": "string"
// }
// },
// "type": "object"
// }
// }
// schemaRef: {
// "properties": {
// "child": {
// "$ref": "#/components/schemas/subpkg_Child"
// },
// "child2": {
// "$ref": "#/components/schemas/openapi3gen_test_Child"
// },
// "children": {
// "$ref": "#/components/schemas/openapi3gen_test_AnotherStruct"
// },
// "field1": {
// "type": "string"
// },
// "field2": {
// "type": "string"
// },
// "field3": {
// "type": "string"
// }
// },
// "type": "object"
// }
}
func ExampleNewSchemaRefForValue_withExportingSchemasIgnoreTopLevelParent() {
type AnotherStruct struct {
Field1 string `json:"field1"`
Field2 string `json:"field2"`
Field3 string `json:"field3"`
}
type RecursiveType struct {
Field1 string `json:"field1"`
Field2 string `json:"field2"`
Field3 string `json:"field3"`
AnotherStruct AnotherStruct `json:"children,omitempty"`
}
schemas := make(openapi3.Schemas)
schemaRef, err := openapi3gen.NewSchemaRefForValue(&RecursiveType{}, schemas, openapi3gen.CreateComponentSchemas(openapi3gen.ExportComponentSchemasOptions{
ExportComponentSchemas: true, ExportTopLevelSchema: false,
}))
if err != nil {
panic(err)
}
var data []byte
if data, err = json.MarshalIndent(&schemas, "", " "); err != nil {
panic(err)
}
fmt.Printf("schemas: %s\n", data)
if data, err = json.MarshalIndent(&schemaRef, "", " "); err != nil {
panic(err)
}
fmt.Printf("schemaRef: %s\n", data)
// Output:
// schemas: {
// "AnotherStruct": {
// "properties": {
// "field1": {
// "type": "string"
// },
// "field2": {
// "type": "string"
// },
// "field3": {
// "type": "string"
// }
// },
// "type": "object"
// }
// }
// schemaRef: {
// "properties": {
// "children": {
// "$ref": "#/components/schemas/AnotherStruct"
// },
// "field1": {
// "type": "string"
// },
// "field2": {
// "type": "string"
// },
// "field3": {
// "type": "string"
// }
// },
// "type": "object"
// }
}
func ExampleNewSchemaRefForValue_withExportingSchemasWithGeneric() {
type Child struct {
Age string `json:"age"`
}
type GenericStruct[T any] struct {
GenericField T `json:"genericField"`
Child Child `json:"child"`
}
type AnotherStruct struct {
Field1 string `json:"field1"`
Field2 string `json:"field2"`
Field3 string `json:"field3"`
}
type RecursiveType struct {
Field1 string `json:"field1"`
Field2 string `json:"field2"`
Field3 string `json:"field3"`
AnotherStruct AnotherStruct `json:"children,omitempty"`
Child Child `json:"child"`
GenericStruct GenericStruct[string] `json:"genericChild"`
}
schemas := make(openapi3.Schemas)
schemaRef, err := openapi3gen.NewSchemaRefForValue(
&RecursiveType{},
schemas,
openapi3gen.CreateComponentSchemas(openapi3gen.ExportComponentSchemasOptions{
ExportComponentSchemas: true, ExportTopLevelSchema: true, ExportGenerics: false,
}),
openapi3gen.UseAllExportedFields(),
)
if err != nil {
panic(err)
}
var data []byte
if data, err = json.MarshalIndent(&schemas, "", " "); err != nil {
panic(err)
}
fmt.Printf("schemas: %s\n", data)
if data, err = json.MarshalIndent(&schemaRef, "", " "); err != nil {
panic(err)
}
fmt.Printf("schemaRef: %s\n", data)
// Output:
// schemas: {
// "AnotherStruct": {
// "properties": {
// "field1": {
// "type": "string"
// },
// "field2": {
// "type": "string"
// },
// "field3": {
// "type": "string"
// }
// },
// "type": "object"
// },
// "Child": {
// "properties": {
// "age": {
// "type": "string"
// }
// },
// "type": "object"
// },
// "RecursiveType": {
// "properties": {
// "child": {
// "$ref": "#/components/schemas/Child"
// },
// "children": {
// "$ref": "#/components/schemas/AnotherStruct"
// },
// "field1": {
// "type": "string"
// },
// "field2": {
// "type": "string"
// },
// "field3": {
// "type": "string"
// },
// "genericChild": {
// "properties": {
// "child": {
// "$ref": "#/components/schemas/Child"
// },
// "genericField": {
// "type": "string"
// }
// },
// "type": "object"
// }
// },
// "type": "object"
// }
// }
// schemaRef: {
// "$ref": "#/components/schemas/RecursiveType"
// }
}
func ExampleNewSchemaRefForValue_withExportingSchemasWithMap() {
type Child struct {
Age string `json:"age"`
}
type MyType struct {
Field1 string `json:"field1"`
Field2 string `json:"field2"`
Map1 map[string]interface{} `json:"anymap"`
Map2 map[string]Child `json:"anymapChild"`
}
schemas := make(openapi3.Schemas)
schemaRef, err := openapi3gen.NewSchemaRefForValue(
&MyType{},
schemas,
openapi3gen.CreateComponentSchemas(openapi3gen.ExportComponentSchemasOptions{
ExportComponentSchemas: true, ExportTopLevelSchema: false, ExportGenerics: true,
}),
openapi3gen.UseAllExportedFields(),
)
if err != nil {
panic(err)
}
var data []byte
if data, err = json.MarshalIndent(&schemas, "", " "); err != nil {
panic(err)
}
fmt.Printf("schemas: %s\n", data)
if data, err = json.MarshalIndent(&schemaRef, "", " "); err != nil {
panic(err)
}
fmt.Printf("schemaRef: %s\n", data)
// Output:
// schemas: {
// "Child": {
// "properties": {
// "age": {
// "type": "string"
// }
// },
// "type": "object"
// }
// }
// schemaRef: {
// "properties": {
// "anymap": {
// "additionalProperties": {},
// "type": "object"
// },
// "anymapChild": {
// "additionalProperties": {
// "$ref": "#/components/schemas/Child"
// },
// "type": "object"
// },
// "field1": {
// "type": "string"
// },
// "field2": {
// "type": "string"
// }
// },
// "type": "object"
// }
}
kin-openapi-0.124.0/openapi3gen/openapi3gen_test.go 0000664 0000000 0000000 00000036406 14604223742 0022113 0 ustar 00root root 0000000 0000000 package openapi3gen_test
import (
"encoding/json"
"errors"
"fmt"
"reflect"
"strconv"
"strings"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/openapi3gen"
)
func ExampleGenerator_SchemaRefs() {
type SomeOtherType string
type Embedded struct {
Z string `json:"z"`
}
type Embedded2 struct {
A string `json:"a"`
}
type EmbeddedNonStruct string
type EmbeddedNonStructPtr string
type Embedded3 struct {
EmbeddedNonStruct
*EmbeddedNonStructPtr
}
type SomeStruct struct {
Bool bool `json:"bool"`
Int int `json:"int"`
Int64 int64 `json:"int64"`
Float64 float64 `json:"float64"`
String string `json:"string"`
Bytes []byte `json:"bytes"`
JSON json.RawMessage `json:"json"`
Time time.Time `json:"time"`
Slice []SomeOtherType `json:"slice"`
Map map[string]*SomeOtherType `json:"map"`
Struct struct {
X string `json:"x"`
} `json:"struct"`
EmptyStruct struct {
Y string
} `json:"structWithoutFields"`
Embedded `json:"embedded"`
Embedded2
Embedded3 `json:"embedded3"`
Ptr *SomeOtherType `json:"ptr"`
}
g := openapi3gen.NewGenerator()
schemaRef, err := g.NewSchemaRefForValue(&SomeStruct{}, nil)
if err != nil {
panic(err)
}
fmt.Printf("g.SchemaRefs: %d\n", len(g.SchemaRefs))
var data []byte
if data, err = json.MarshalIndent(&schemaRef, "", " "); err != nil {
panic(err)
}
fmt.Printf("schemaRef: %s\n", data)
// Output:
// g.SchemaRefs: 17
// schemaRef: {
// "properties": {
// "a": {
// "type": "string"
// },
// "bool": {
// "type": "boolean"
// },
// "bytes": {
// "format": "byte",
// "type": "string"
// },
// "embedded": {
// "properties": {
// "z": {
// "type": "string"
// }
// },
// "type": "object"
// },
// "embedded3": {},
// "float64": {
// "format": "double",
// "type": "number"
// },
// "int": {
// "type": "integer"
// },
// "int64": {
// "format": "int64",
// "type": "integer"
// },
// "json": {},
// "map": {
// "additionalProperties": {
// "type": "string"
// },
// "type": "object"
// },
// "ptr": {
// "type": "string"
// },
// "slice": {
// "items": {
// "type": "string"
// },
// "type": "array"
// },
// "string": {
// "type": "string"
// },
// "struct": {
// "properties": {
// "x": {
// "type": "string"
// }
// },
// "type": "object"
// },
// "structWithoutFields": {},
// "time": {
// "format": "date-time",
// "type": "string"
// }
// },
// "type": "object"
// }
}
func ExampleThrowErrorOnCycle() {
type CyclicType0 struct {
CyclicField *struct {
CyclicField *CyclicType0 `json:"b"`
} `json:"a"`
}
schemas := make(openapi3.Schemas)
schemaRef, err := openapi3gen.NewSchemaRefForValue(&CyclicType0{}, schemas, openapi3gen.ThrowErrorOnCycle())
if schemaRef != nil || err == nil {
panic(`With option ThrowErrorOnCycle, an error is returned when a schema reference cycle is found`)
}
if _, ok := err.(*openapi3gen.CycleError); !ok {
panic(`With option ThrowErrorOnCycle, an error of type CycleError is returned`)
}
if len(schemas) != 0 {
panic(`No references should have been collected at this point`)
}
if schemaRef, err = openapi3gen.NewSchemaRefForValue(&CyclicType0{}, schemas); err != nil {
panic(err)
}
var data []byte
if data, err = json.MarshalIndent(schemaRef, "", " "); err != nil {
panic(err)
}
fmt.Printf("schemaRef: %s\n", data)
if data, err = json.MarshalIndent(schemas, "", " "); err != nil {
panic(err)
}
fmt.Printf("schemas: %s\n", data)
// Output:
// schemaRef: {
// "properties": {
// "a": {
// "properties": {
// "b": {
// "$ref": "#/components/schemas/CyclicType0"
// }
// },
// "type": "object"
// }
// },
// "type": "object"
// }
// schemas: {
// "CyclicType0": {
// "properties": {
// "a": {
// "properties": {
// "b": {
// "$ref": "#/components/schemas/CyclicType0"
// }
// },
// "type": "object"
// }
// },
// "type": "object"
// }
// }
}
func TestExportedNonTagged(t *testing.T) {
type Bla struct {
A string
Another string `json:"another"`
yetAnother string // unused because unexported
EvenAYaml string `yaml:"even_a_yaml"`
}
schemaRef, err := openapi3gen.NewSchemaRefForValue(&Bla{}, nil, openapi3gen.UseAllExportedFields())
require.NoError(t, err)
require.Equal(t, &openapi3.SchemaRef{Value: &openapi3.Schema{
Type: &openapi3.Types{"object"},
Properties: map[string]*openapi3.SchemaRef{
"A": {Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}},
"another": {Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}},
"even_a_yaml": {Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}},
}}}, schemaRef)
}
func ExampleUseAllExportedFields() {
type UnsignedIntStruct struct {
UnsignedInt uint `json:"uint"`
}
schemaRef, err := openapi3gen.NewSchemaRefForValue(&UnsignedIntStruct{}, nil, openapi3gen.UseAllExportedFields())
if err != nil {
panic(err)
}
var data []byte
if data, err = json.MarshalIndent(schemaRef, "", " "); err != nil {
panic(err)
}
fmt.Printf("schemaRef: %s\n", data)
// Output:
// schemaRef: {
// "properties": {
// "uint": {
// "minimum": 0,
// "type": "integer"
// }
// },
// "type": "object"
// }
}
func ExampleGenerator_GenerateSchemaRef() {
type EmbeddedStruct struct {
ID string
}
type ContainerStruct struct {
Name string
EmbeddedStruct
}
instance := &ContainerStruct{
Name: "Container",
EmbeddedStruct: EmbeddedStruct{
ID: "Embedded",
},
}
generator := openapi3gen.NewGenerator(openapi3gen.UseAllExportedFields())
schemaRef, err := generator.GenerateSchemaRef(reflect.TypeOf(instance))
if err != nil {
panic(err)
}
var data []byte
if data, err = json.MarshalIndent(schemaRef.Value.Properties["Name"].Value, "", " "); err != nil {
panic(err)
}
fmt.Printf(`schemaRef.Value.Properties["Name"].Value: %s`, data)
fmt.Println()
if data, err = json.MarshalIndent(schemaRef.Value.Properties["ID"].Value, "", " "); err != nil {
panic(err)
}
fmt.Printf(`schemaRef.Value.Properties["ID"].Value: %s`, data)
fmt.Println()
// Output:
// schemaRef.Value.Properties["Name"].Value: {
// "type": "string"
// }
// schemaRef.Value.Properties["ID"].Value: {
// "type": "string"
// }
}
func TestEmbeddedPointerStructs(t *testing.T) {
type EmbeddedStruct struct {
ID string
}
type ContainerStruct struct {
Name string
*EmbeddedStruct
}
instance := &ContainerStruct{
Name: "Container",
EmbeddedStruct: &EmbeddedStruct{
ID: "Embedded",
},
}
generator := openapi3gen.NewGenerator(openapi3gen.UseAllExportedFields())
schemaRef, err := generator.GenerateSchemaRef(reflect.TypeOf(instance))
require.NoError(t, err)
var ok bool
_, ok = schemaRef.Value.Properties["Name"]
require.Equal(t, true, ok)
_, ok = schemaRef.Value.Properties["ID"]
require.Equal(t, true, ok)
}
// See: https://github.com/getkin/kin-openapi/issues/500
func TestEmbeddedPointerStructsWithSchemaCustomizer(t *testing.T) {
type EmbeddedStruct struct {
ID string
}
type ContainerStruct struct {
Name string
*EmbeddedStruct
}
instance := &ContainerStruct{
Name: "Container",
EmbeddedStruct: &EmbeddedStruct{
ID: "Embedded",
},
}
customizerFn := func(name string, t reflect.Type, tag reflect.StructTag, schema *openapi3.Schema) error {
return nil
}
customizerOpt := openapi3gen.SchemaCustomizer(customizerFn)
generator := openapi3gen.NewGenerator(openapi3gen.UseAllExportedFields(), customizerOpt)
schemaRef, err := generator.GenerateSchemaRef(reflect.TypeOf(instance))
require.NoError(t, err)
var ok bool
_, ok = schemaRef.Value.Properties["Name"]
require.Equal(t, true, ok)
_, ok = schemaRef.Value.Properties["ID"]
require.Equal(t, true, ok)
}
func TestCyclicReferences(t *testing.T) {
type ObjectDiff struct {
FieldCycle *ObjectDiff
SliceCycle []*ObjectDiff
MapCycle map[*ObjectDiff]*ObjectDiff
}
instance := &ObjectDiff{
FieldCycle: nil,
SliceCycle: nil,
MapCycle: nil,
}
generator := openapi3gen.NewGenerator(openapi3gen.UseAllExportedFields())
schemaRef, err := generator.GenerateSchemaRef(reflect.TypeOf(instance))
require.NoError(t, err)
require.NotNil(t, schemaRef.Value.Properties["FieldCycle"])
require.Equal(t, "#/components/schemas/ObjectDiff", schemaRef.Value.Properties["FieldCycle"].Ref)
require.NotNil(t, schemaRef.Value.Properties["SliceCycle"])
require.Equal(t, &openapi3.Types{"array"}, schemaRef.Value.Properties["SliceCycle"].Value.Type)
require.Equal(t, "#/components/schemas/ObjectDiff", schemaRef.Value.Properties["SliceCycle"].Value.Items.Ref)
require.NotNil(t, schemaRef.Value.Properties["MapCycle"])
require.Equal(t, &openapi3.Types{"object"}, schemaRef.Value.Properties["MapCycle"].Value.Type)
require.Equal(t, "#/components/schemas/ObjectDiff", schemaRef.Value.Properties["MapCycle"].Value.AdditionalProperties.Schema.Ref)
}
func ExampleSchemaCustomizer() {
type NestedInnerBla struct {
Enum1Field string `json:"enum1" myenumtag:"a,b"`
}
type InnerBla struct {
UntaggedStringField string
AnonStruct struct {
InnerFieldWithoutTag int
InnerFieldWithTag int `mymintag:"-1" mymaxtag:"50"`
NestedInnerBla
}
Enum2Field string `json:"enum2" myenumtag:"c,d"`
}
type Bla struct {
InnerBla
EnumField3 string `json:"enum3" myenumtag:"e,f"`
}
customizer := openapi3gen.SchemaCustomizer(func(name string, ft reflect.Type, tag reflect.StructTag, schema *openapi3.Schema) error {
if tag.Get("mymintag") != "" {
minVal, err := strconv.ParseFloat(tag.Get("mymintag"), 64)
if err != nil {
return err
}
schema.Min = &minVal
}
if tag.Get("mymaxtag") != "" {
maxVal, err := strconv.ParseFloat(tag.Get("mymaxtag"), 64)
if err != nil {
return err
}
schema.Max = &maxVal
}
if tag.Get("myenumtag") != "" {
for _, s := range strings.Split(tag.Get("myenumtag"), ",") {
schema.Enum = append(schema.Enum, s)
}
}
return nil
})
schemaRef, err := openapi3gen.NewSchemaRefForValue(&Bla{}, nil, openapi3gen.UseAllExportedFields(), customizer)
if err != nil {
panic(err)
}
var data []byte
if data, err = json.MarshalIndent(schemaRef, "", " "); err != nil {
panic(err)
}
fmt.Printf("schemaRef: %s\n", data)
// Output:
// schemaRef: {
// "properties": {
// "AnonStruct": {
// "properties": {
// "InnerFieldWithTag": {
// "maximum": 50,
// "minimum": -1,
// "type": "integer"
// },
// "InnerFieldWithoutTag": {
// "type": "integer"
// },
// "enum1": {
// "enum": [
// "a",
// "b"
// ],
// "type": "string"
// }
// },
// "type": "object"
// },
// "UntaggedStringField": {
// "type": "string"
// },
// "enum2": {
// "enum": [
// "c",
// "d"
// ],
// "type": "string"
// },
// "enum3": {
// "enum": [
// "e",
// "f"
// ],
// "type": "string"
// }
// },
// "type": "object"
// }
}
func TestSchemaCustomizerError(t *testing.T) {
customizer := openapi3gen.SchemaCustomizer(func(name string, ft reflect.Type, tag reflect.StructTag, schema *openapi3.Schema) error {
return errors.New("test error")
})
type Bla struct{}
_, err := openapi3gen.NewSchemaRefForValue(&Bla{}, nil, openapi3gen.UseAllExportedFields(), customizer)
require.EqualError(t, err, "test error")
}
func TestSchemaCustomizerExcludeSchema(t *testing.T) {
type Bla struct {
Str string
}
customizer := openapi3gen.SchemaCustomizer(func(name string, ft reflect.Type, tag reflect.StructTag, schema *openapi3.Schema) error {
return nil
})
schema, err := openapi3gen.NewSchemaRefForValue(&Bla{}, nil, openapi3gen.UseAllExportedFields(), customizer)
require.NoError(t, err)
require.Equal(t, &openapi3.SchemaRef{Value: &openapi3.Schema{
Type: &openapi3.Types{"object"},
Properties: map[string]*openapi3.SchemaRef{
"Str": {Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}},
}}}, schema)
customizer = openapi3gen.SchemaCustomizer(func(name string, ft reflect.Type, tag reflect.StructTag, schema *openapi3.Schema) error {
return &openapi3gen.ExcludeSchemaSentinel{}
})
schema, err = openapi3gen.NewSchemaRefForValue(&Bla{}, nil, openapi3gen.UseAllExportedFields(), customizer)
require.NoError(t, err)
require.Nil(t, schema)
}
func ExampleNewSchemaRefForValue_recursive() {
type RecursiveType struct {
Field1 string `json:"field1"`
Field2 string `json:"field2"`
Field3 string `json:"field3"`
Components []*RecursiveType `json:"children,omitempty"`
}
schemas := make(openapi3.Schemas)
schemaRef, err := openapi3gen.NewSchemaRefForValue(&RecursiveType{}, schemas)
if err != nil {
panic(err)
}
var data []byte
if data, err = json.MarshalIndent(&schemas, "", " "); err != nil {
panic(err)
}
fmt.Printf("schemas: %s\n", data)
if data, err = json.MarshalIndent(&schemaRef, "", " "); err != nil {
panic(err)
}
fmt.Printf("schemaRef: %s\n", data)
// Output:
// schemas: {
// "RecursiveType": {
// "properties": {
// "children": {
// "items": {
// "$ref": "#/components/schemas/RecursiveType"
// },
// "type": "array"
// },
// "field1": {
// "type": "string"
// },
// "field2": {
// "type": "string"
// },
// "field3": {
// "type": "string"
// }
// },
// "type": "object"
// }
// }
// schemaRef: {
// "properties": {
// "children": {
// "items": {
// "$ref": "#/components/schemas/RecursiveType"
// },
// "type": "array"
// },
// "field1": {
// "type": "string"
// },
// "field2": {
// "type": "string"
// },
// "field3": {
// "type": "string"
// }
// },
// "type": "object"
// }
}
type ID [16]byte
// T implements SetSchemar, allowing it to set an OpenAPI schema.
type T struct {
ID ID `json:"id"`
}
func (_ *ID) SetSchema(schema *openapi3.Schema) {
schema.Type = &openapi3.Types{"string"} // Assuming this matches your custom implementation
schema.Format = "uuid"
}
func ExampleSetSchemar() {
schemas := make(openapi3.Schemas)
instance := &T{
ID: ID{},
}
// Generate the schema for the instance
schemaRef, err := openapi3gen.NewSchemaRefForValue(instance, schemas)
if err != nil {
panic(err)
}
data, err := json.MarshalIndent(schemaRef, "", " ")
if err != nil {
panic(err)
}
fmt.Printf("schemaRef: %s\n", data)
// Output:
// schemaRef: {
// "properties": {
// "id": {
// "format": "uuid",
// "type": "string"
// }
// },
// "type": "object"
// }
}
kin-openapi-0.124.0/openapi3gen/simple_test.go 0000664 0000000 0000000 00000004234 14604223742 0021166 0 ustar 00root root 0000000 0000000 package openapi3gen_test
import (
"encoding/json"
"fmt"
"time"
"github.com/getkin/kin-openapi/openapi3gen"
)
type (
SomeStruct struct {
Bool bool `json:"bool"`
Int int `json:"int"`
Int64 int64 `json:"int64"`
Float64 float64 `json:"float64"`
String string `json:"string"`
Bytes []byte `json:"bytes"`
JSON json.RawMessage `json:"json"`
Time time.Time `json:"time"`
Slice []SomeOtherType `json:"slice"`
Map map[string]*SomeOtherType `json:"map"`
Struct struct {
X string `json:"x"`
} `json:"struct"`
EmptyStruct struct {
Y string
} `json:"structWithoutFields"`
Ptr *SomeOtherType `json:"ptr"`
}
SomeOtherType string
)
func Example() {
schemaRef, err := openapi3gen.NewSchemaRefForValue(&SomeStruct{}, nil)
if err != nil {
panic(err)
}
data, err := json.MarshalIndent(schemaRef, "", " ")
if err != nil {
panic(err)
}
fmt.Printf("%s\n", data)
// Output:
// {
// "properties": {
// "bool": {
// "type": "boolean"
// },
// "bytes": {
// "format": "byte",
// "type": "string"
// },
// "float64": {
// "format": "double",
// "type": "number"
// },
// "int": {
// "type": "integer"
// },
// "int64": {
// "format": "int64",
// "type": "integer"
// },
// "json": {},
// "map": {
// "additionalProperties": {
// "type": "string"
// },
// "type": "object"
// },
// "ptr": {
// "type": "string"
// },
// "slice": {
// "items": {
// "type": "string"
// },
// "type": "array"
// },
// "string": {
// "type": "string"
// },
// "struct": {
// "properties": {
// "x": {
// "type": "string"
// }
// },
// "type": "object"
// },
// "structWithoutFields": {},
// "time": {
// "format": "date-time",
// "type": "string"
// }
// },
// "type": "object"
// }
}
kin-openapi-0.124.0/openapi3gen/type_info.go 0000664 0000000 0000000 00000001731 14604223742 0020631 0 ustar 00root root 0000000 0000000 package openapi3gen
import (
"reflect"
"sort"
"sync"
)
var (
typeInfos = map[reflect.Type]*theTypeInfo{}
typeInfosMutex sync.RWMutex
)
// theTypeInfo contains information about JSON serialization of a type
type theTypeInfo struct {
Type reflect.Type
Fields []theFieldInfo
}
// getTypeInfo returns theTypeInfo for the given type.
func getTypeInfo(t reflect.Type) *theTypeInfo {
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
typeInfosMutex.RLock()
typeInfo, exists := typeInfos[t]
typeInfosMutex.RUnlock()
if exists {
return typeInfo
}
if t.Kind() != reflect.Struct {
typeInfo = &theTypeInfo{
Type: t,
}
} else {
// Allocate
typeInfo = &theTypeInfo{
Type: t,
Fields: make([]theFieldInfo, 0, 16),
}
// Add fields
typeInfo.Fields = appendFields(nil, nil, t)
// Sort fields
sort.Sort(sortableFieldInfos(typeInfo.Fields))
}
// Publish
typeInfosMutex.Lock()
typeInfos[t] = typeInfo
typeInfosMutex.Unlock()
return typeInfo
}
kin-openapi-0.124.0/refs.sh 0000775 0000000 0000000 00000006301 14604223742 0015372 0 ustar 00root root 0000000 0000000 #!/bin/bash -eux
set -o pipefail
types=()
types+=("Callback")
types+=("Example")
types+=("Header")
types+=("Link")
types+=("Parameter")
types+=("RequestBody")
types+=("Response")
types+=("Schema")
types+=("SecurityScheme")
cat <