pax_global_header00006660000000000000000000000064146042237420014516gustar00rootroot0000000000000052 comment=9dbb4c3be9314ca0985a3fd32f393368cd998957 kin-openapi-0.124.0/000077500000000000000000000000001460422374200140745ustar00rootroot00000000000000kin-openapi-0.124.0/.gitattributes000066400000000000000000000000221460422374200167610ustar00rootroot00000000000000*.yml text eol=lf kin-openapi-0.124.0/.github/000077500000000000000000000000001460422374200154345ustar00rootroot00000000000000kin-openapi-0.124.0/.github/FUNDING.yml000066400000000000000000000013321460422374200172500ustar00rootroot00000000000000# 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/000077500000000000000000000000001460422374200163645ustar00rootroot00000000000000kin-openapi-0.124.0/.github/docs/openapi2.txt000066400000000000000000000230051460422374200206420ustar00rootroot00000000000000package 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.txt000066400000000000000000000056711460422374200215410ustar00rootroot00000000000000package 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.txt000066400000000000000000002174441460422374200206570ustar00rootroot00000000000000package 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.txt000066400000000000000000000424071460422374200220600ustar00rootroot00000000000000package 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.txt000066400000000000000000000066171460422374200213470ustar00rootroot00000000000000package 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.txt000066400000000000000000000024031460422374200206270ustar00rootroot00000000000000package 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.txt000066400000000000000000000016541460422374200231010ustar00rootroot00000000000000package 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.txt000066400000000000000000000024301460422374200221530ustar00rootroot00000000000000package 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.txt000066400000000000000000000041121460422374200245640ustar00rootroot00000000000000package 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/000077500000000000000000000000001460422374200173225ustar00rootroot00000000000000kin-openapi-0.124.0/.github/sponsors/speakeasy-github-sponsor-dark.svg000066400000000000000000000460711460422374200257400ustar00rootroot00000000000000 kin-openapi-0.124.0/.github/sponsors/speakeasy-github-sponsor-light.svg000066400000000000000000000460671460422374200261330ustar00rootroot00000000000000 kin-openapi-0.124.0/.github/workflows/000077500000000000000000000000001460422374200174715ustar00rootroot00000000000000kin-openapi-0.124.0/.github/workflows/codeql.yml000066400000000000000000000015001460422374200214570ustar00rootroot00000000000000name: "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.yml000066400000000000000000000147041460422374200206270ustar00rootroot00000000000000name: 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.yml000066400000000000000000000004501460422374200223200ustar00rootroot00000000000000name: 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/.gitignore000066400000000000000000000005271460422374200160700ustar00rootroot00000000000000# 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/LICENSE000066400000000000000000000020721460422374200151020ustar00rootroot00000000000000MIT 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.md000066400000000000000000000376431460422374200153700ustar00rootroot00000000000000[![CI](https://github.com/getkin/kin-openapi/workflows/go/badge.svg)](https://github.com/getkin/kin-openapi/actions) [![Go Report Card](https://goreportcard.com/badge/github.com/getkin/kin-openapi)](https://goreportcard.com/report/github.com/getkin/kin-openapi) [![GoDoc](https://godoc.org/github.com/getkin/kin-openapi?status.svg)](https://godoc.org/github.com/getkin/kin-openapi) [![Join Gitter Chat Channel -](https://badges.gitter.im/getkin/kin.svg)](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:

Speakeasy logo

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/000077500000000000000000000000001460422374200146375ustar00rootroot00000000000000kin-openapi-0.124.0/cmd/validate/000077500000000000000000000000001460422374200164305ustar00rootroot00000000000000kin-openapi-0.124.0/cmd/validate/main.go000066400000000000000000000057761460422374200177220ustar00rootroot00000000000000package 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.sh000077500000000000000000000013021460422374200153570ustar00rootroot00000000000000#!/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.mod000066400000000000000000000011741460422374200152050ustar00rootroot00000000000000module 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.sum000066400000000000000000000055201460422374200152310ustar00rootroot00000000000000github.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.sh000077500000000000000000000133011460422374200153710ustar00rootroot00000000000000#!/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/000077500000000000000000000000001460422374200156115ustar00rootroot00000000000000kin-openapi-0.124.0/openapi2/doc.go000066400000000000000000000005021460422374200167020ustar00rootroot00000000000000// 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.go000066400000000000000000000005401460422374200173670ustar00rootroot00000000000000package 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.go000066400000000000000000000013111460422374200172460ustar00rootroot00000000000000package 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.go000066400000000000000000000020401460422374200203050ustar00rootroot00000000000000package 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.go000066400000000000000000000076321460422374200176650ustar00rootroot00000000000000package 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.go000066400000000000000000000021061460422374200207130ustar00rootroot00000000000000package 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.go000066400000000000000000000060021460422374200201360ustar00rootroot00000000000000package 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.go000066400000000000000000000131671460422374200201300ustar00rootroot00000000000000package 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.go000066400000000000000000000075361460422374200201250ustar00rootroot00000000000000package 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.go000066400000000000000000000032751460422374200200050ustar00rootroot00000000000000package 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.go000066400000000000000000000053321460422374200213360ustar00rootroot00000000000000package 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/000077500000000000000000000000001460422374200174225ustar00rootroot00000000000000kin-openapi-0.124.0/openapi2/testdata/swagger.json000066400000000000000000000321521460422374200217570ustar00rootroot00000000000000{"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/000077500000000000000000000000001460422374200164775ustar00rootroot00000000000000kin-openapi-0.124.0/openapi2conv/doc.go000066400000000000000000000001421460422374200175700ustar00rootroot00000000000000// Package openapi2conv converts an OpenAPI v2 specification document to v3. package openapi2conv kin-openapi-0.124.0/openapi2conv/issue187_test.go000066400000000000000000000114471460422374200214640ustar00rootroot00000000000000package 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.go000066400000000000000000000022051460422374200214440ustar00rootroot00000000000000package 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.go000066400000000000000000000012611460422374200214570ustar00rootroot00000000000000package 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.go000066400000000000000000000023761460422374200214640ustar00rootroot00000000000000package 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.go000066400000000000000000000015341460422374200214630ustar00rootroot00000000000000package 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.go000066400000000000000000001065571460422374200216060ustar00rootroot00000000000000package 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.go000066400000000000000000000403031460422374200226270ustar00rootroot00000000000000package 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/000077500000000000000000000000001460422374200203105ustar00rootroot00000000000000kin-openapi-0.124.0/openapi2conv/testdata/swagger.json000077700000000000000000000000001460422374200311432../../openapi2/testdata/swagger.jsonustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/000077500000000000000000000000001460422374200156125ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/additionalProperties_test.go000066400000000000000000000015051460422374200233660ustar00rootroot00000000000000package 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.go000066400000000000000000000026071460422374200177020ustar00rootroot00000000000000package 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.go000066400000000000000000000254361460422374200203400ustar00rootroot00000000000000package 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.go000066400000000000000000000031041460422374200175720ustar00rootroot00000000000000package 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.go000066400000000000000000000062011460422374200176120ustar00rootroot00000000000000package 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.go000066400000000000000000000043541460422374200206600ustar00rootroot00000000000000package 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.go000066400000000000000000000031671460422374200210170ustar00rootroot00000000000000package 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.go000066400000000000000000000017301460422374200220500ustar00rootroot00000000000000package 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.go000066400000000000000000000002571460422374200167120ustar00rootroot00000000000000// 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.go000066400000000000000000000076341460422374200177410ustar00rootroot00000000000000package 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.go000066400000000000000000000044561460422374200207770ustar00rootroot00000000000000package 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.go000066400000000000000000000023731460422374200174620ustar00rootroot00000000000000package 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.go000066400000000000000000000042671460422374200176050ustar00rootroot00000000000000package 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.go000066400000000000000000000021031460422374200206270ustar00rootroot00000000000000package 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.go000066400000000000000000000006731460422374200220140ustar00rootroot00000000000000package 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.go000066400000000000000000000310221460422374200230430ustar00rootroot00000000000000package 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.go000066400000000000000000000011241460422374200201530ustar00rootroot00000000000000package 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.go000066400000000000000000000032571460422374200210020ustar00rootroot00000000000000package 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.go000066400000000000000000000016171460422374200220370ustar00rootroot00000000000000package 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.go000066400000000000000000000056001460422374200173720ustar00rootroot00000000000000package 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.go000066400000000000000000000022431460422374200176040ustar00rootroot00000000000000package 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.go000066400000000000000000000047251460422374200171040ustar00rootroot00000000000000package 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.go000066400000000000000000000330571460422374200215140ustar00rootroot00000000000000package 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.go000066400000000000000000000035471460422374200225540ustar00rootroot00000000000000package 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.go000066400000000000000000000015471460422374200205710ustar00rootroot00000000000000package 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.go000066400000000000000000000011011460422374200205500ustar00rootroot00000000000000package 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.go000066400000000000000000000014701460422374200205560ustar00rootroot00000000000000package 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.go000066400000000000000000000026111460422374200205600ustar00rootroot00000000000000package 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.go000066400000000000000000000006621460422374200205670ustar00rootroot00000000000000package 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.go000066400000000000000000000071021460422374200205700ustar00rootroot00000000000000package 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.go000066400000000000000000000005031460422374200205630ustar00rootroot00000000000000package 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.go000066400000000000000000000061501460422374200205740ustar00rootroot00000000000000package 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.go000066400000000000000000000133101460422374200205570ustar00rootroot00000000000000package 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.go000066400000000000000000000010731460422374200205640ustar00rootroot00000000000000package 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.go000066400000000000000000000003741460422374200205700ustar00rootroot00000000000000package 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.go000066400000000000000000000013621460422374200205740ustar00rootroot00000000000000package 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.go000066400000000000000000000020001460422374200205470ustar00rootroot00000000000000package 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.go000066400000000000000000000016221460422374200205650ustar00rootroot00000000000000package 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.go000066400000000000000000000014451460422374200205730ustar00rootroot00000000000000package 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.go000066400000000000000000000011471460422374200205740ustar00rootroot00000000000000package 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.go000066400000000000000000000015361460422374200205720ustar00rootroot00000000000000package 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.go000066400000000000000000000033031460422374200205710ustar00rootroot00000000000000package 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.go000066400000000000000000000072731460422374200206100ustar00rootroot00000000000000package 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.go000066400000000000000000000004341460422374200205770ustar00rootroot00000000000000package 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.go000066400000000000000000000147631460422374200206020ustar00rootroot00000000000000package 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.go000066400000000000000000000020251460422374200205630ustar00rootroot00000000000000package 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.go000066400000000000000000000011331460422374200205670ustar00rootroot00000000000000package 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.go000066400000000000000000000012511460422374200205660ustar00rootroot00000000000000package 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.go000066400000000000000000000012021460422374200205700ustar00rootroot00000000000000package 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.go000066400000000000000000000053201460422374200205740ustar00rootroot00000000000000package 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.go000066400000000000000000000004371460422374200206000ustar00rootroot00000000000000package 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.go000066400000000000000000000007151460422374200206010ustar00rootroot00000000000000package 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.go000066400000000000000000000016021460422374200205710ustar00rootroot00000000000000package 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.go000066400000000000000000000044221460422374200205750ustar00rootroot00000000000000package 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.go000066400000000000000000000027741460422374200175750ustar00rootroot00000000000000package 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.go000066400000000000000000000050541460422374200171020ustar00rootroot00000000000000package 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.go000066400000000000000000000033451460422374200264160ustar00rootroot00000000000000//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.go000066400000000000000000000014611460422374200227750ustar00rootroot00000000000000//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.go000066400000000000000000000724301460422374200174150ustar00rootroot00000000000000package 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.go000066400000000000000000000036661460422374200262200ustar00rootroot00000000000000package 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.go000066400000000000000000000056101460422374200227200ustar00rootroot00000000000000package 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.go000066400000000000000000000043201460422374200221020ustar00rootroot00000000000000package 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.go000066400000000000000000000012451460422374200221040ustar00rootroot00000000000000package 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.go000066400000000000000000000012011460422374200221020ustar00rootroot00000000000000package 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.go000066400000000000000000000024231460422374200232220ustar00rootroot00000000000000package 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.go000066400000000000000000000013511460422374200216450ustar00rootroot00000000000000package 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.go000066400000000000000000000047571460422374200243730ustar00rootroot00000000000000package 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.go000066400000000000000000000027151460422374200233760ustar00rootroot00000000000000package 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.go000066400000000000000000000661441460422374200233730ustar00rootroot00000000000000package 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.go000066400000000000000000000436221460422374200204550ustar00rootroot00000000000000package 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.go000066400000000000000000000066411460422374200216170ustar00rootroot00000000000000package 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.go000066400000000000000000000210611460422374200175630ustar00rootroot00000000000000package 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.go000066400000000000000000000057501460422374200206310ustar00rootroot00000000000000package 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.go000066400000000000000000000013111460422374200172470ustar00rootroot00000000000000package 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.go000066400000000000000000000026771460422374200203260ustar00rootroot00000000000000package 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.go000066400000000000000000000110271460422374200202620ustar00rootroot00000000000000package 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.go000066400000000000000000000027361460422374200213300ustar00rootroot00000000000000package 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.go000066400000000000000000000120331460422374200176560ustar00rootroot00000000000000package 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.go000066400000000000000000000256651460422374200207340ustar00rootroot00000000000000package 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.go000066400000000000000000000134561460422374200201520ustar00rootroot00000000000000package 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.go000066400000000000000000000034771460422374200212130ustar00rootroot00000000000000package 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.go000066400000000000000000000277351460422374200201370ustar00rootroot00000000000000package 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.go000066400000000000000000000051161460422374200226220ustar00rootroot00000000000000package 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.go000066400000000000000000000047061460422374200226360ustar00rootroot00000000000000package 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.go000066400000000000000000000146471460422374200201270ustar00rootroot00000000000000package 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.go000066400000000000000000000163151460422374200172660ustar00rootroot00000000000000package 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.go000066400000000000000000000033141460422374200203200ustar00rootroot00000000000000package 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.go000066400000000000000000000014271460422374200201160ustar00rootroot00000000000000package 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.go000066400000000000000000000003421460422374200167140ustar00rootroot00000000000000package 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.go000066400000000000000000000500271460422374200171040ustar00rootroot00000000000000package 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.go000066400000000000000000000202231460422374200201360ustar00rootroot00000000000000package 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.go000066400000000000000000000072211460422374200206500ustar00rootroot00000000000000package 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.go000066400000000000000000000144721460422374200200070ustar00rootroot00000000000000package 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.go000066400000000000000000000346401460422374200225050ustar00rootroot00000000000000package 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.go000066400000000000000000001622221460422374200174060ustar00rootroot00000000000000package 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.go000066400000000000000000000054761460422374200211500ustar00rootroot00000000000000package 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.go000066400000000000000000000066331460422374200222030ustar00rootroot00000000000000package 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.go000066400000000000000000000022051460422374200221120ustar00rootroot00000000000000package 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.go000066400000000000000000000023021460422374200221040ustar00rootroot00000000000000package 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.go000066400000000000000000000116411460422374200215710ustar00rootroot00000000000000package 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.go000066400000000000000000000014701460422374200211400ustar00rootroot00000000000000package 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.go000066400000000000000000000010721460422374200221750ustar00rootroot00000000000000package 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.go000066400000000000000000000750261460422374200204520ustar00rootroot00000000000000package 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.go000066400000000000000000000057361460422374200235460ustar00rootroot00000000000000package 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.go000066400000000000000000000013451460422374200245750ustar00rootroot00000000000000package 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.go000066400000000000000000000027651460422374200226250ustar00rootroot00000000000000package 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.go000066400000000000000000000025551460422374200236610ustar00rootroot00000000000000package 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.go000066400000000000000000000271001460422374200213340ustar00rootroot00000000000000package 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.go000066400000000000000000000075451460422374200224060ustar00rootroot00000000000000package 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.go000066400000000000000000000007461460422374200223650ustar00rootroot00000000000000package 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.go000066400000000000000000000163431460422374200174560ustar00rootroot00000000000000package 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.go000066400000000000000000000121161460422374200205070ustar00rootroot00000000000000package 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.go000066400000000000000000000043221460422374200167150ustar00rootroot00000000000000package 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/000077500000000000000000000000001460422374200174235ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/303bis/000077500000000000000000000000001460422374200204265ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/303bis/common/000077500000000000000000000000001460422374200217165ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/303bis/common/properties.yaml000066400000000000000000000004671460422374200250050ustar00rootroot00000000000000timestamp: 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.yaml000066400000000000000000000011051460422374200227470ustar00rootroot00000000000000openapi: 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.yml000066400000000000000000000015771460422374200241560ustar00rootroot00000000000000openapi: 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.yml000066400000000000000000000003711460422374200245370ustar00rootroot00000000000000post: 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.yml000066400000000000000000000033011460422374200220620ustar00rootroot00000000000000openapi: 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.yml000066400000000000000000000061101460422374200253520ustar00rootroot00000000000000{ "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/000077500000000000000000000000001460422374200216645ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/circularRef/base.yml000066400000000000000000000005171460422374200233240ustar00rootroot00000000000000openapi: "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.yml000066400000000000000000000002451460422374200235310ustar00rootroot00000000000000openapi: "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.yml000066400000000000000000000005731460422374200241060ustar00rootroot00000000000000--- 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.json000066400000000000000000000032621460422374200241400ustar00rootroot00000000000000{ "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.yml000066400000000000000000000016541460422374200237730ustar00rootroot00000000000000--- 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.yml000066400000000000000000000051541460422374200214170ustar00rootroot00000000000000id: 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.json000066400000000000000000000006021460422374200211140ustar00rootroot00000000000000{ "$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.yml000066400000000000000000000007661460422374200235430ustar00rootroot00000000000000openapi: 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.yml000066400000000000000000000007611460422374200225450ustar00rootroot00000000000000openapi: 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.yml000066400000000000000000000003651460422374200225460ustar00rootroot00000000000000components: 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.yml000066400000000000000000000001561460422374200225450ustar00rootroot00000000000000components: schemas: ObjectX: type: object properties: name: type: string kin-openapi-0.124.0/openapi3/testdata/issue241.yml000066400000000000000000000003701460422374200215250ustar00rootroot00000000000000openapi: 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.yml000066400000000000000000000010521460422374200215310ustar00rootroot00000000000000openapi: 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.json000066400000000000000000004613251460422374200217150ustar00rootroot00000000000000{ "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/000077500000000000000000000000001460422374200210145ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/issue638/test1.yaml000066400000000000000000000005251460422374200227420ustar00rootroot00000000000000openapi: "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.yaml000066400000000000000000000004011460422374200227340ustar00rootroot00000000000000openapi: "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/000077500000000000000000000000001460422374200210105ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/issue652/definitions.yml000066400000000000000000000001061460422374200240430ustar00rootroot00000000000000components: schemas: TestSchema: type: string kin-openapi-0.124.0/openapi3/testdata/issue652/nested/000077500000000000000000000000001460422374200222725ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/issue652/nested/schema.yml000066400000000000000000000002031460422374200242500ustar00rootroot00000000000000components: schemas: ReferenceToParentDirectory: $ref: "../definitions.yml#/components/schemas/TestSchema" kin-openapi-0.124.0/openapi3/testdata/issue697.yml000066400000000000000000000003361460422374200215460ustar00rootroot00000000000000openapi: 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.yml000066400000000000000000000022571460422374200215430ustar00rootroot00000000000000openapi: '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.yml000066400000000000000000000003251460422374200215420ustar00rootroot00000000000000openapi: 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.yml000066400000000000000000010325421460422374200215530ustar00rootroot00000000000000openapi: 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: objectkin-openapi-0.124.0/openapi3/testdata/issue831/000077500000000000000000000000001460422374200210075ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/issue831/path.yml000066400000000000000000000000621460422374200224640ustar00rootroot00000000000000get: responses: "200": description: OKkin-openapi-0.124.0/openapi3/testdata/issue831/testref.internalizepath.openapi.yml000066400000000000000000000007771460422374200300530ustar00rootroot00000000000000openapi: "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: stringkin-openapi-0.124.0/openapi3/testdata/issue831/testref.internalizepath.openapi.yml.internalized.yml000066400000000000000000000023551460422374200333340ustar00rootroot00000000000000{ "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.yaml000066400000000000000000000121601460422374200226750ustar00rootroot00000000000000openapi: 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.yaml000066400000000000000000001231511460422374200214510ustar00rootroot00000000000000# 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.yaml000066400000000000000000000001751460422374200212360ustar00rootroot00000000000000openapi: "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.json000066400000000000000000000004471460422374200224010ustar00rootroot00000000000000{ "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.json000066400000000000000000000011471460422374200235160ustar00rootroot00000000000000{ "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/000077500000000000000000000000001460422374200214045ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/nesteddir/nestedcomponents.openapi.json000066400000000000000000000024751460422374200273310ustar00rootroot00000000000000{ "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.json000066400000000000000000000017101460422374200300150ustar00rootroot00000000000000{ "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.json000066400000000000000000057120711460422374200236100ustar00rootroot00000000000000{ "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 \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
Ahoy, World!

{{Sender_Name}}

{{Sender_Address}}, {{Sender_City}}, {{Sender_State}} {{Sender_Zip}}

Unsubscribe - Unsubscribe Preferences

\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 \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
Ahoy, World!

{{Sender_Name}}

{{Sender_Address}}, {{Sender_City}}, {{Sender_State}} {{Sender_Zip}}

Unsubscribe - Unsubscribe Preferences

\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 \n \n \n \n \n
\n \n \n \n
\n

You've found the secret!

\n
\n \n \n \n
\"Off
\n \n \n \n
\"\"
\n \n \n \n
Welcome to the family!
\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?
Browse Gallery
\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
\n \n \"Facebook\"\n \n \n \n \"Twitter\"\n \n \n \n \"Instagram\"\n \n \n \n \"Pinterest\"\n \n
\n

{{Sender_Name}}

{{Sender_Address}}, {{Sender_City}}, {{Sender_State}} {{Sender_Zip}}

Unsubscribe - Unsubscribe Preferences

\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 \n \n \n \n \n
\n \n \n \n
\n

You've found the secret!

\n
\n \n \n \n
\"Off
\n \n \n \n
\"\"
\n \n \n \n
Welcome to the family!
\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?
Browse Gallery
\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
\n \n \"Facebook\"\n \n \n \n \"Twitter\"\n \n \n \n \"Instagram\"\n \n \n \n \"Pinterest\"\n \n
\n

{{Sender_Name}}

{{Sender_Address}}, {{Sender_City}}, {{Sender_State}} {{Sender_Zip}}

Unsubscribe - Unsubscribe Preferences

\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 \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
Ahoy, World!

{{Sender_Name}}

{{Sender_Address}}, {{Sender_City}}, {{Sender_State}} {{Sender_Zip}}

Unsubscribe - Unsubscribe Preferences

\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 \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
Ahoy, World!

{{Sender_Name}}

{{Sender_Address}}, {{Sender_City}}, {{Sender_State}} {{Sender_Zip}}

Unsubscribe - Unsubscribe Preferences

\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 \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
Ahoy, World!

{{Sender_Name}}

{{Sender_Address}}, {{Sender_City}}, {{Sender_State}} {{Sender_Zip}}

Unsubscribe - Unsubscribe Preferences

\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 \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
Ahoy, World!

{{Sender_Name}}

{{Sender_Address}}, {{Sender_City}}, {{Sender_State}} {{Sender_Zip}}

Unsubscribe - Unsubscribe Preferences

\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\n\n\n\n\n\n
AlertsMail SettingsTeammates
API KeysMailTemplates
ASM GroupsMarketing CampaignsSuppressions
BillingPartner SettingsTracking
CategoriesScheduled SendsUser Settings
StatsWebhookIPs
SubusersDomain AuthenticationReverse DNS
Admin API Key Scopes
\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 \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\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\n\n \n \n\n \n
Error CodeError MessageDetails
400
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.
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.
You must have at least one personalization.
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.
There is a limit of 1000 total recipients (to + cc + bcc) per request.
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.
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\"}
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.
Each unique email address in the personalizations array should only be included once. You have included [email address] more than once.
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.
The to parameter is required for all personalization objects.
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

personalizations.bcc

\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 \n\n\n \n \n\n\n\n \n \n \n\n\n \n \n\n\n\n \n
Error CodeError MessageDetails
400
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\"}
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.
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\"}
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.
Each unique email address in the personalization block should only be included once. You have included [email address] more than once.
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

personalizations.cc

\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 \n\n\n \n \n\n\n\n \n \n \n\n\n \n \n\n\n\n \n
Error CodeError MessageDetails
400
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\"}
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.
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\"}
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.
Each unique email address in the personalization block should only be included once. You have included [email address] more than once.
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

personalizations.custom_args

\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 \n\n\n \n \n\n\n\n \n \n \n\n\n \n \n\n\n\n \n
Error CodeError MessageDetails
400
All values of custom arguments object must be strings
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.
custom_args cannot be empty.
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.
The combined size of both global and personalization custom arguments may not exceed 10,000 bytes per personalization.
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

personalizations.headers

\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 \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
Error CodeError MessageDetails
400
All values of the headers object must be strings.
The object type of every header that you include must be a string. You cannot include headers that are integers, booleans, or arrays.
The headers cannot contain duplicate keys.
You may not include the same header more than once.
Header keys cannot contain non-ASCII characters or empty spaces.
When defining the headers that you would like to use, you must make sure that the string contains only ASCII characters.
Header cannot be one of the reserved keys.
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

personalizations.send_at

\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 \n\n\n \n \n\n\n\n \n
Error CodeError MessageDetails
400
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.
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.
Scheduling more than 72 hours in advance is forbidden.
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

personalizations.subject

\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 \n\n\n \n \n\n\n\n \n
Error CodeError MessageDetails
400
The subject of your email must be a string at least one character in length.
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.
The subject is required. You can get around this requirement if you use a template with a subject defined.
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

personalizations.substitutions

\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 \n\n\n \n \n\n\n\n \n
Error CodeError MessageDetails
400
The substitution values must be an object of key/value pairs, where the values are all strings.
Substitutions must always follow the pattern \"substitution_tag\": \"value to substitute\". The value to substitute for the \"substitution_tag\" must always be a string.
You are limited to 10,000 substitutions.
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

personalizations.to

\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
Error CodeError MessageDetails
400
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\"}
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

ASM Errors

\n\n\n\n\n

asm.group_id

\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 \n\n\n \n \n\n\n\n \n
Error CodeError MessageDetails
400
The ASM group ID must be an integer.
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.
The ASM group ID must be a valid Group ID on your account. You provided [YOUR ASM GROUP ID].
\n\n\n\n\n

asm.groups_to_display

\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 \n\n\n \n \n\n\n\n \n \n \n\n \n \n\n\n\n \n
Error CodeError MessageDetails
400
The ASM Group IDs to display must be an array of integers.
All ASM groups to display must be valid ASM groups IDs on your account. You provided {invalid IDs}.
There is a limit of 25 unsubscribe groups that can be displayed to a user at a time.\n
\n\n\n\n\n

Attachment Errors

\n\n\n\n\n

attachments.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 \n\n\n \n \n\n\n\n \n
Error CodeError MessageDetails
400
The attachment content must be base64 encoded.
The attachment content must be a string at least one character in length.
\n\n\n\n\n

attachments.content_id

\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 \n\n\n \n \n\n\n\n \n
Error CodeError MessageDetails
400
The content ID of your attachment must be a string. You provided [YOUR CONTENT ID].
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>
The content ID of your attachment cannot contain CRLF characters.
When defining your content_id, you may not include the characters ;, ,, n, or r.
\n\n\n\n\n

attachments.disposition

\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
Error CodeError MessageDetails
400
The disposition of your attachment can be either \"inline\" or \"attachment\". You provided [YOUR DISPOSITION].
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

attachments.filename

\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 \n\n\n \n \n\n\n\n \n \n \n\n\n \n \n\n\n\n \n
Error CodeError MessageDetails
400
The filename of your attachment must be a string.
The filename of your attachment cannot contain CRLF characters.
When defining the filename of your attachment, you may not include the characters ;, ,, n, or r.
filename is required.
You must always include a filename for your attachment.
\n\n\n\n\n

attachments.type

\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 \n\n\n \n \n\n\n\n \n
Error CodeError MessageDetails
400
The attachment type must be a string and at least one character in length.
The type cannot contain ‘;’, or CRLF characters.
When defining the type of your attachment content, you may not include the characters ;, ,, n, or r.
\n\n\n\n\n

Batch ID Errors

\n\n\n\n\n

batch_id

\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
Error CodeError MessageDetails
400
The batch_id must be a string.
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

Categories Errors

\n\n\n\n\n

categories

\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 \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 \n \n\n\n\n \n
Error CodeError MessageDetails
400
There is a limit of 10 categories for each email that is sent. You provided X categories.
For more information on how you can use categories to organize your email analytics, please visit our Categories documentation.
Categories must be an array of strings.
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.
Each category must not be longer than 255 characters. [YOUR CATEGORY] exceeds this limit
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.
The categories must be a unique list, and you have included [YOUR CATEGORY] more than once.
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.
Categories can not contain non-ASCII characters.
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

Content Errors

\n\n\n\n\n

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 \n\n\n \n \n\n\n\n \n
Error CodeError MessageDetails
400
The content param is required unless you are using a transactional template and have defined a template_ID.
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.
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.
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

content.type

\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 \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
Error CodeError MessageDetails
400
A content type is required, this tells email clients how to display the email.
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.
The content value must be a string at least one character in length.
You may not send an email with no content.
Your content type must be a string with length of at least one character.
The content of your email must always be contained within a string when you make your request.
Cannot contain ‘;’, or CRLF characters.
When defining the type of your content, you may not include the characters ;, ,, n, or r.
\n\n\n\n\n

content.value

\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 \n\n\n \n \n\n\n\n \n \n \n\n\n \n \n\n\n\n \n
Error CodeError MessageDetails
400
A content value is required, this is the content of the email you are sending.
We do not allow you to send an empty email to your recipients. You must always include a value for your content.
The content value must be a string at least one character in length.
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.
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.
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

Encoding Errors

\n\n\n\n\n

Encoding

\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
Error CodeError MessageDetails
415
Invalid UTF8 in request
Your payload must be encoded in UTF-8. This includes any attachments.
\n\n\n\n\n

From Address Errors

\n\n\n\n\n

from

\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 \n\n\n \n \n\n\n\n \n
Error CodeError MessageDetails
400
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\"}
While every from parameter must include a valid email address, you are not required to include a name.
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\"}
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

Headers Errors

\n\n\n\n\n

headers

\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 \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
Error CodeError MessageDetails
400
The header values must be strings.
The object type of every header that you include must be a string. You cannot include headers that are integers, booleans, or arrays.
The headers cannot contain duplicate keys.
You may not include the same header more than once.
Header keys cannot contain non-ASCII characters and empty spaces.
When defining the headers that you would like to use, you must make sure that the string contains only ASCII characters.
Header can not be one of the reserved keys.
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

IP Pool Errors

\n\n\n\n\n

ip_pool_name

\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 \n\n\n \n \n\n\n\n \n
Error CodeError MessageDetails
400
The name of your IP pool must be a string.
The IP Pool name must be a valid pool name for your account. You provided [YOUR IP POOL NAME].
\n\n\n\n\n

Reply To Errors

\n\n\n\n\n

reply_to

\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
Error CodeError MessageDetails
400
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\"}
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

Sections Errors

\n\n\n\n\n

sections

\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
Error CodeError MessageDetails
400
The section values must be strings.
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

Send At Errors

\n\n\n\n\n

send_at

\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 \n\n\n \n \n\n\n\n \n
Error CodeError MessageDetails
400
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.
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.
Scheduling more than 72 hours in advance is forbidden.
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

Subject Line Errors

\n\n\n\n\n

subject

\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 \n\n\n \n \n\n\n\n \n
Error CodeError MessageDetails
400
The subject of your email must be a string at least one character in length.
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.
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.
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

Template Errors

\n\n\n\n\n

template_id

\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 \n\n\n \n \n\n\n\n \n \n \n\n\n \n \n\n\n\n \n
Error CodeError MessageDetails
400
The template ID must be a string, you provided [YOUR TEMPLATE ID].
Template IDs are always strings.
The Template ID must be a valid template id for your account. You provided [YOUR TEMPLATE ID].
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.
Must be a valid template.
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

Mail Settings Errors

\n\n\n\n\n

mail_settings.bcc.email

\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 \n\n\n \n \n\n\n\n \n
Error CodeError MessageDetails
400
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\"}
You must include a recipient object when using the bcc mail setting.
\n\n\n\n\n

mail_settings.bcc.enable

\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
Error CodeError MessageDetails
400
The bcc enable param should be a boolean value.
\n\n\n\n\n

mail_settings.bypass_list_management.enable

\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
Error CodeError MessageDetails
400
The bypass list management enable param should be a boolean value.
\n\n\n\n\n

mail_settings.footer.enable

\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
Error CodeError MessageDetails
400
The footer enable param should be a boolean value.
\n\n\n\n\n

mail_settings.footer.html

\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
Error CodeError MessageDetails
400
The text/html version of your footer should be a string.
\n\n\n\n\n

mail_settings.footer.text

\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
Error CodeError MessageDetails
400
The text/plain version of your footer should be a string.
\n\n\n\n\n

mail_settings.sandbox_mode.enable

\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
Error CodeError MessageDetails
400
The sandbox mode enable param should be a boolean value.
\n\n\n\n\n

mail_settings.spam_check.enable

\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
Error CodeError MessageDetails
400
The spam check enable param should be a boolean value.
\n\n\n\n\n

mail_settings.spam_check.post_to_url

\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 \n\n\n \n \n\n\n\n \n \n \n\n\n \n \n\n\n\n \n
Error CodeError MessageDetails
400
The spam check url must be a string.
You must include the url to post to when using the spam check mail setting.
The `post_to_url` parameter must start with `http://` or `https://`.
\n\n\n\n\n

mail_settings.spam_check.threshold

\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 \n\n\n \n \n\n\n\n \n
Error CodeError MessageDetails
400
The spam check threshold is between 1 and 10, with the lower numbers being the most strict filtering.
You must include the spam check threshold when using the spam check mail setting.
\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 \n \n \n \n \n \n \n \n \n\n \n\n\n \n \n \n\n\n \n \n\n\n\n \n
Error CodeError MessageDetails
400
The click tracking enable param should be a boolean value.
\n\n\n\n\n

tracking_settings.click_tracking.enable_text

\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
Error CodeError MessageDetails
400
The click tracking enable text must be a boolean value.
\n\n\n\n\n

tracking_settings.ganalytics.enable

\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
Error CodeError MessageDetails
400
The Google Analytics enable param must be a boolean value.
\n\n\n\n\n

tracking_settings.ganalytics.utm_campaign

\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
Error CodeError MessageDetails
400
The Google Analytics utm_campaign must be a string value.
\n\n\n\n\n

tracking_settings.ganalytics.utm_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
Error CodeError MessageDetails
400
The Google Analytics utm_content must be a string value.
\n\n\n\n\n

tracking_settings.ganalytics.utm_medium

\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
Error CodeError MessageDetails
400
The Google Analytics utm_medium must be a string value.
\n\n\n\n\n

tracking_settings.ganalytics.utm_source

\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
Error CodeError MessageDetails
400
The Google Analytics utm_source must be a string value.
\n\n\n\n\n

tracking_settings.ganalytics.utm_term

\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
Error CodeError MessageDetails
400
The Google Analytics utm_term must be a string value.
\n\n\n\n\n

tracking_settings.open_tracking.enable

\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
Error CodeError MessageDetails
400
The open tracking enable param should be a boolean value.
\n\n\n\n\n

tracking_settings.open_tracking.substitution_tag

\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
Error CodeError MessageDetails
400
The open tracking substitution tag must be a string.
\n\n\n\n\n

tracking_settings.subscription_tracking.enable

\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
Error CodeError MessageDetails
400
The subscription tracking enable param should be a boolean value.
\n\n\n\n\n

tracking_settings.subscription_tracking.html

\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
Error CodeError MessageDetails
400
The subscription tracking unsubscribe content for text/html messages must be a string.
\n\n\n\n\n

tracking_settings.subscription_tracking.substitution_tag

\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
Error CodeError MessageDetails
400
The subscription tracking substitution tag must be a string value.
\n\n\n\n\n

tracking_settings.subscription_tracking.text

\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
Error CodeError MessageDetails
400
The subscription tracking unsubscribe content for text/plain messages must be a string.
\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.yml000066400000000000000000000003131460422374200232260ustar00rootroot00000000000000--- 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/000077500000000000000000000000001460422374200220675ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/recursiveRef/components/000077500000000000000000000000001460422374200242545ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/recursiveRef/components/Bar.yml000066400000000000000000000000321460422374200254760ustar00rootroot00000000000000type: string example: bar kin-openapi-0.124.0/openapi3/testdata/recursiveRef/components/Cat.yml000066400000000000000000000001211460422374200255000ustar00rootroot00000000000000type: object properties: cat: $ref: ../openapi.yml#/components/schemas/Cat kin-openapi-0.124.0/openapi3/testdata/recursiveRef/components/Foo.yml000066400000000000000000000001211460422374200255140ustar00rootroot00000000000000type: object properties: bar: $ref: ../openapi.yml#/components/schemas/Bar kin-openapi-0.124.0/openapi3/testdata/recursiveRef/components/Foo/000077500000000000000000000000001460422374200247775ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/recursiveRef/components/Foo/Foo2.yml000066400000000000000000000001241460422374200263240ustar00rootroot00000000000000type: object properties: foo: $ref: ../../openapi.yml#/components/schemas/Foo kin-openapi-0.124.0/openapi3/testdata/recursiveRef/components/models/000077500000000000000000000000001460422374200255375ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/recursiveRef/components/models/error.yaml000066400000000000000000000000411460422374200275470ustar00rootroot00000000000000type: object title: ErrorDetails kin-openapi-0.124.0/openapi3/testdata/recursiveRef/issue615.yml000066400000000000000000000030301460422374200241720ustar00rootroot00000000000000openapi: "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: booleankin-openapi-0.124.0/openapi3/testdata/recursiveRef/openapi.yml000066400000000000000000000013321460422374200242440ustar00rootroot00000000000000openapi: "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.yml000066400000000000000000000043051460422374200275360ustar00rootroot00000000000000{ "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/000077500000000000000000000000001460422374200242325ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/recursiveRef/parameters/number.yml000066400000000000000000000000621460422374200262430ustar00rootroot00000000000000name: someNumber in: query schema: type: string kin-openapi-0.124.0/openapi3/testdata/recursiveRef/paths/000077500000000000000000000000001460422374200232065ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/recursiveRef/paths/foo.yml000066400000000000000000000005421460422374200245150ustar00rootroot00000000000000parameters: - $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/000077500000000000000000000000001460422374200220765ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/refInLocalRef/messages/000077500000000000000000000000001460422374200237055ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/refInLocalRef/messages/data.json000066400000000000000000000002571460422374200255150ustar00rootroot00000000000000{ "type": "object", "properties": { "id": { "type": "integer", "format": "int32" }, "ref_prop_part": { "$ref": "./dataPart.json" } } } kin-openapi-0.124.0/openapi3/testdata/refInLocalRef/messages/dataPart.json000066400000000000000000000001651460422374200263420ustar00rootroot00000000000000{ "type": "object", "properties": { "idPart": { "type": "integer", "format": "int64" } } } kin-openapi-0.124.0/openapi3/testdata/refInLocalRef/messages/request.json000066400000000000000000000002361460422374200262710ustar00rootroot00000000000000{ "type": "object", "required": [ "definition_reference" ], "properties": { "definition_reference": { "$ref": "./data.json" } } } kin-openapi-0.124.0/openapi3/testdata/refInLocalRef/messages/response.json000066400000000000000000000001611460422374200264340ustar00rootroot00000000000000{ "type": "object", "properties": { "id": { "type": "integer", "format": "int32" } } } kin-openapi-0.124.0/openapi3/testdata/refInLocalRef/openapi.json000066400000000000000000000016761460422374200244360ustar00rootroot00000000000000{ "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/000077500000000000000000000000001460422374200250735ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/refInLocalRefInParentsSubdir/messages/000077500000000000000000000000001460422374200267025ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/refInLocalRefInParentsSubdir/messages/data.json000066400000000000000000000002571460422374200305120ustar00rootroot00000000000000{ "type": "object", "properties": { "id": { "type": "integer", "format": "int32" }, "ref_prop_part": { "$ref": "./dataPart.json" } } } kin-openapi-0.124.0/openapi3/testdata/refInLocalRefInParentsSubdir/messages/dataPart.json000066400000000000000000000001651460422374200313370ustar00rootroot00000000000000{ "type": "object", "properties": { "idPart": { "type": "integer", "format": "int64" } } } kin-openapi-0.124.0/openapi3/testdata/refInLocalRefInParentsSubdir/messages/request.json000066400000000000000000000002361460422374200312660ustar00rootroot00000000000000{ "type": "object", "required": [ "definition_reference" ], "properties": { "definition_reference": { "$ref": "./data.json" } } } kin-openapi-0.124.0/openapi3/testdata/refInLocalRefInParentsSubdir/messages/response.json000066400000000000000000000001611460422374200314310ustar00rootroot00000000000000{ "type": "object", "properties": { "id": { "type": "integer", "format": "int32" } } } kin-openapi-0.124.0/openapi3/testdata/refInLocalRefInParentsSubdir/spec/000077500000000000000000000000001460422374200260255ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/refInLocalRefInParentsSubdir/spec/openapi.json000066400000000000000000000017071460422374200303600ustar00rootroot00000000000000{ "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/000077500000000000000000000000001460422374200211235ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/refInRef/messages/000077500000000000000000000000001460422374200227325ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/refInRef/messages/definitions.json000066400000000000000000000001121460422374200261320ustar00rootroot00000000000000{ "definitions": { "External": { "type": "string" } } } kin-openapi-0.124.0/openapi3/testdata/refInRef/messages/request.json000066400000000000000000000002711460422374200253150ustar00rootroot00000000000000{ "type": "object", "required": [ "definition_reference" ], "properties": { "definition_reference": { "$ref": "definitions.json#/definitions/External" } } } kin-openapi-0.124.0/openapi3/testdata/refInRef/messages/response.json000066400000000000000000000001611460422374200254610ustar00rootroot00000000000000{ "type": "object", "properties": { "id": { "type": "integer", "format": "int32" } } } kin-openapi-0.124.0/openapi3/testdata/refInRef/openapi.json000066400000000000000000000012751460422374200234560ustar00rootroot00000000000000{ "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/000077500000000000000000000000001460422374200231575ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/refInRefInProperty/common-data-objects/000077500000000000000000000000001460422374200270055ustar00rootroot00000000000000problem-details-0.0.1.schema.json000066400000000000000000000056171460422374200346060ustar00rootroot00000000000000kin-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/000077500000000000000000000000001460422374200253445ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/refInRefInProperty/components/errors.yaml000066400000000000000000000033711460422374200275500ustar00rootroot00000000000000components: 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.yaml000066400000000000000000000014221460422374200254750ustar00rootroot00000000000000openapi: 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/000077500000000000000000000000001460422374200220475ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/relativeDocs/CustomTestExample.yml000066400000000000000000000001151460422374200262150ustar00rootroot00000000000000summary: An example value: | { "example": "hello" }kin-openapi-0.124.0/openapi3/testdata/relativeDocs/CustomTestHeader.yml000066400000000000000000000000301460422374200260060ustar00rootroot00000000000000description: descriptionkin-openapi-0.124.0/openapi3/testdata/relativeDocs/CustomTestParameter.yml000066400000000000000000000000441460422374200265430ustar00rootroot00000000000000name: param in: path required: true kin-openapi-0.124.0/openapi3/testdata/relativeDocs/CustomTestPath.yml000066400000000000000000000004741460422374200255260ustar00rootroot00000000000000get: 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.yml000066400000000000000000000000341460422374200270700ustar00rootroot00000000000000description: example requestkin-openapi-0.124.0/openapi3/testdata/relativeDocs/CustomTestResponse.yml000066400000000000000000000000301460422374200264140ustar00rootroot00000000000000description: descriptionkin-openapi-0.124.0/openapi3/testdata/relativeDocs/CustomTestSchema.yml000066400000000000000000000000141460422374200260200ustar00rootroot00000000000000type: stringkin-openapi-0.124.0/openapi3/testdata/relativeDocs/CustomTestSecurityScheme.yml000066400000000000000000000000301460422374200275520ustar00rootroot00000000000000type: http scheme: basickin-openapi-0.124.0/openapi3/testdata/relativeDocs/openapi/000077500000000000000000000000001460422374200235025ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/relativeDocs/openapi/openapi.yml000066400000000000000000000002361460422374200256610ustar00rootroot00000000000000openapi: 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/000077500000000000000000000000001460422374200255235ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/relativeDocs/openapi/responses/custom/000077500000000000000000000000001460422374200270355ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/relativeDocs/openapi/responses/custom/CustomTestResponse.yml000066400000000000000000000001561460422374200334130ustar00rootroot00000000000000description: description content: application/json: schema: $ref: "../../../CustomTestSchema.yml" kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/000077500000000000000000000000001460422374200250405ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestExample.yml000066400000000000000000000000401460422374200312030ustar00rootroot00000000000000summary: An example value: hellokin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestHeader.yml000066400000000000000000000000241460422374200310020ustar00rootroot00000000000000description: header kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestHeader1.yml000066400000000000000000000000251460422374200310640ustar00rootroot00000000000000description: header1 kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestHeader1bis.yml000066400000000000000000000000371460422374200315650ustar00rootroot00000000000000header: description: header1 kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestHeader2.yml000066400000000000000000000000251460422374200310650ustar00rootroot00000000000000description: header2 kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestHeader2bis.yml000066400000000000000000000000371460422374200315660ustar00rootroot00000000000000header: description: header2 kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestParameter.yml000066400000000000000000000000441460422374200315340ustar00rootroot00000000000000name: param in: path required: true kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestRequestBody.yml000066400000000000000000000000341460422374200320610ustar00rootroot00000000000000description: example requestkin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestResponse.yml000066400000000000000000000000301460422374200314050ustar00rootroot00000000000000description: descriptionkin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestSchema.yml000066400000000000000000000000141460422374200310110ustar00rootroot00000000000000type: stringkin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/openapi/000077500000000000000000000000001460422374200264735ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/openapi/openapi.yml000066400000000000000000000003241460422374200306500ustar00rootroot00000000000000openapi: 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/000077500000000000000000000000001460422374200276125ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/openapi/paths/nesteddir/000077500000000000000000000000001460422374200315735ustar00rootroot00000000000000CustomTestPath.yml000066400000000000000000000012131460422374200351630ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/openapi/paths/nesteddirpatch: 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/000077500000000000000000000000001460422374200336615ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/openapi/paths/nesteddirCustomTestPath.yml000066400000000000000000000012721460422374200373350ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/openapi/paths/nesteddir/morenestedpatch: 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/000077500000000000000000000000001460422374200305145ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/openapi/responses/nesteddir/000077500000000000000000000000001460422374200324755ustar00rootroot00000000000000CustomTestResponse.yml000066400000000000000000000003751460422374200367770ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3/testdata/relativeDocsUseDocumentPath/openapi/responses/nesteddirdescription: 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.yml000066400000000000000000000022701460422374200216460ustar00rootroot00000000000000components: 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.json000066400000000000000000000000741460422374200250110ustar00rootroot00000000000000{ "description": "this is a single response definition" } kin-openapi-0.124.0/openapi3/testdata/singleresponse.openapi.yml000066400000000000000000000000661460422374200246420ustar00rootroot00000000000000--- description: this is a single response definition kin-openapi-0.124.0/openapi3/testdata/spec.yaml000066400000000000000000000003351460422374200212420ustar00rootroot00000000000000openapi: 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.yml000066400000000000000000000011541460422374200245310ustar00rootroot00000000000000{ "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.yml000066400000000000000000000004721460422374200270060ustar00rootroot00000000000000openapi: 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.json000066400000000000000000000003661460422374200227340ustar00rootroot00000000000000{ "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.yml000066400000000000000000000002231460422374200225540ustar00rootroot00000000000000--- 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.yaml000066400000000000000000000004521460422374200221440ustar00rootroot00000000000000paths: /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.json000066400000000000000000000005641460422374200234310ustar00rootroot00000000000000{ "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.yml000066400000000000000000000003641460422374200232570ustar00rootroot00000000000000--- 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.yml000066400000000000000000000004531460422374200265450ustar00rootroot00000000000000{ "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.json000066400000000000000000000004131460422374200265470ustar00rootroot00000000000000{ "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.yml000066400000000000000000000002631460422374200264020ustar00rootroot00000000000000--- 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.go000066400000000000000000000016631460422374200234010ustar00rootroot00000000000000package 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.go000066400000000000000000000022771460422374200230070ustar00rootroot00000000000000package 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.go000066400000000000000000000101561460422374200220510ustar00rootroot00000000000000package 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.go000066400000000000000000000016631460422374200176160ustar00rootroot00000000000000package 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.go000066400000000000000000000035171460422374200167470ustar00rootroot00000000000000package 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/000077500000000000000000000000001460422374200170205ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3filter/authentication_input.go000066400000000000000000000013141460422374200236040ustar00rootroot00000000000000package 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.go000066400000000000000000000044771460422374200235600ustar00rootroot00000000000000package 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.go000066400000000000000000000042161460422374200206660ustar00rootroot00000000000000package 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.go000066400000000000000000000007201460422374200211620ustar00rootroot00000000000000package 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.go000066400000000000000000000060121460422374200217600ustar00rootroot00000000000000package 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.go000066400000000000000000000204631460422374200220020ustar00rootroot00000000000000package 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.go000066400000000000000000000054561460422374200220050ustar00rootroot00000000000000package 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.go000066400000000000000000000027511460422374200217770ustar00rootroot00000000000000package 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.go000066400000000000000000000055171460422374200220030ustar00rootroot00000000000000package 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.go000066400000000000000000000044511460422374200220040ustar00rootroot00000000000000package 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.go000066400000000000000000000045131460422374200217740ustar00rootroot00000000000000package 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.go000066400000000000000000000105571460422374200220150ustar00rootroot00000000000000package 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.go000066400000000000000000000036461460422374200220050ustar00rootroot00000000000000package 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.go000066400000000000000000000053541460422374200220000ustar00rootroot00000000000000package 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.go000066400000000000000000000051601460422374200217750ustar00rootroot00000000000000package 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.go000066400000000000000000000060741460422374200220150ustar00rootroot00000000000000package 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.go000066400000000000000000000051171460422374200220060ustar00rootroot00000000000000package 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.go000066400000000000000000000165201460422374200214700ustar00rootroot00000000000000package 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.go000066400000000000000000000374441460422374200225370ustar00rootroot00000000000000package 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.go000066400000000000000000000031651460422374200210470ustar00rootroot00000000000000package 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.go000066400000000000000000000033441460422374200221050ustar00rootroot00000000000000package 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.go000066400000000000000000001324401460422374200226600ustar00rootroot00000000000000package 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.go000066400000000000000000001722151460422374200237230ustar00rootroot00000000000000package 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.go000066400000000000000000000027701460422374200226740ustar00rootroot00000000000000package 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.go000066400000000000000000000020541460422374200237260ustar00rootroot00000000000000package 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/000077500000000000000000000000001460422374200206315ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3filter/testdata/fixtures/000077500000000000000000000000001460422374200225025ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3filter/testdata/fixtures/petstore.json000066400000000000000000001144541460422374200252530ustar00rootroot00000000000000{ "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.yaml000066400000000000000000000050541460422374200233660ustar00rootroot00000000000000openapi: "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.go000066400000000000000000000100051460422374200232570ustar00rootroot00000000000000package 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.go000066400000000000000000000124131460422374200240750ustar00rootroot00000000000000package 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.go000066400000000000000000000277421460422374200227240ustar00rootroot00000000000000package 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.go000066400000000000000000000050131460422374200254610ustar00rootroot00000000000000package 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.go000066400000000000000000000022241460422374200241270ustar00rootroot00000000000000package 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.go000066400000000000000000000370741460422374200237620ustar00rootroot00000000000000package 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.go000066400000000000000000000140221460422374200230550ustar00rootroot00000000000000// 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.go000066400000000000000000000014631460422374200243010ustar00rootroot00000000000000package 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.go000066400000000000000000000136001460422374200241150ustar00rootroot00000000000000package 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.go000066400000000000000000000470121460422374200245620ustar00rootroot00000000000000package 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.go000066400000000000000000000042571460422374200254770ustar00rootroot00000000000000package 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.go000066400000000000000000000110771460422374200235720ustar00rootroot00000000000000package 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.go000066400000000000000000000051551460422374200227200ustar00rootroot00000000000000package 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.go000066400000000000000000000127661460422374200244250ustar00rootroot00000000000000package 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.go000066400000000000000000000657001460422374200237610ustar00rootroot00000000000000package 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.go000066400000000000000000000051511460422374200232000ustar00rootroot00000000000000package 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.go000066400000000000000000000070401460422374200223510ustar00rootroot00000000000000package 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.go000066400000000000000000000530531460422374200225460ustar00rootroot00000000000000package 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.go000066400000000000000000000065261460422374200235640ustar00rootroot00000000000000package 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/000077500000000000000000000000001460422374200163045ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3gen/field_info.go000066400000000000000000000045421460422374200207360ustar00rootroot00000000000000package 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/000077500000000000000000000000001460422374200201205ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3gen/internal/subpkg/000077500000000000000000000000001460422374200214135ustar00rootroot00000000000000kin-openapi-0.124.0/openapi3gen/internal/subpkg/sub_type.go000066400000000000000000000001011460422374200235640ustar00rootroot00000000000000package subpkg type Child struct { Name string `yaml:"name"` } kin-openapi-0.124.0/openapi3gen/openapi3gen.go000066400000000000000000000347201460422374200210510ustar00rootroot00000000000000// 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.go000066400000000000000000000245011460422374200263770ustar00rootroot00000000000000package 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.go000066400000000000000000000364061460422374200221130ustar00rootroot00000000000000package 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.go000066400000000000000000000042341460422374200211660ustar00rootroot00000000000000package 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.go000066400000000000000000000017311460422374200206310ustar00rootroot00000000000000package 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.sh000077500000000000000000000063011460422374200153720ustar00rootroot00000000000000#!/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 < 0 { if servers, err = makeServers(pathItem.Servers); err != nil { return nil, err } } operations := pathItem.Operations() methods := make([]string, 0, len(operations)) for method := range operations { methods = append(methods, method) } sort.Strings(methods) for _, s := range servers { muxRoute := muxRouter.Path(s.base + path).Methods(methods...) if schemes := s.schemes; len(schemes) != 0 { muxRoute.Schemes(schemes...) } if host := s.host; host != "" { muxRoute.Host(host) } if err := muxRoute.GetError(); err != nil { return nil, err } r.muxes = append(r.muxes, routeMux{ muxRoute: muxRoute, varsUpdater: s.varsUpdater, }) r.routes = append(r.routes, &routers.Route{ Spec: doc, Server: s.server, Path: path, PathItem: pathItem, Method: "", Operation: nil, }) } } return r, nil } // FindRoute extracts the route and parameters of an http.Request func (r *Router) FindRoute(req *http.Request) (*routers.Route, map[string]string, error) { for i, m := range r.muxes { var match mux.RouteMatch if m.muxRoute.Match(req, &match) { if err := match.MatchErr; err != nil { // What then? } vars := match.Vars if f := m.varsUpdater; f != nil { f(vars) } route := *r.routes[i] route.Method = req.Method route.Operation = route.Spec.Paths.Value(route.Path).GetOperation(route.Method) return &route, vars, nil } switch match.MatchErr { case nil: case mux.ErrMethodMismatch: return nil, nil, routers.ErrMethodNotAllowed default: // What then? } } return nil, nil, routers.ErrPathNotFound } func makeServers(in openapi3.Servers) ([]srv, error) { servers := make([]srv, 0, len(in)) for _, server := range in { serverURL := server.URL if submatch := singleVariableMatcher.FindStringSubmatch(serverURL); submatch != nil { sVar := submatch[1] sVal := server.Variables[sVar].Default serverURL = strings.ReplaceAll(serverURL, "{"+sVar+"}", sVal) var varsUpdater varsf if lhs := strings.TrimSuffix(serverURL, server.Variables[sVar].Default); lhs != "" { varsUpdater = func(vars map[string]string) { vars[sVar] = lhs } } svr, err := newSrv(serverURL, server, varsUpdater) if err != nil { return nil, err } servers = append(servers, svr) continue } // If a variable represents the port "http://domain.tld:{port}/bla" // then url.Parse() cannot parse "http://domain.tld:`bEncode({port})`/bla" // and mux is not able to set the {port} variable // So we just use the default value for this variable. // See https://github.com/getkin/kin-openapi/issues/367 var varsUpdater varsf if lhs := strings.Index(serverURL, ":{"); lhs > 0 { rest := serverURL[lhs+len(":{"):] rhs := strings.Index(rest, "}") portVariable := rest[:rhs] portValue := server.Variables[portVariable].Default serverURL = strings.ReplaceAll(serverURL, "{"+portVariable+"}", portValue) varsUpdater = func(vars map[string]string) { vars[portVariable] = portValue } } svr, err := newSrv(serverURL, server, varsUpdater) if err != nil { return nil, err } servers = append(servers, svr) } if len(servers) == 0 { servers = append(servers, srv{}) } return servers, nil } func newSrv(serverURL string, server *openapi3.Server, varsUpdater varsf) (srv, error) { var schemes []string if strings.Contains(serverURL, "://") { scheme0 := strings.Split(serverURL, "://")[0] schemes = permutePart(scheme0, server) serverURL = strings.Replace(serverURL, scheme0+"://", schemes[0]+"://", 1) } u, err := url.Parse(bEncode(serverURL)) if err != nil { return srv{}, err } path := bDecode(u.EscapedPath()) if len(path) > 0 && path[len(path)-1] == '/' { path = path[:len(path)-1] } svr := srv{ host: bDecode(u.Host), //u.Hostname()? base: path, schemes: schemes, // scheme: []string{scheme0}, TODO: https://github.com/gorilla/mux/issues/624 server: server, varsUpdater: varsUpdater, } return svr, nil } // Magic strings that temporarily replace "{}" so net/url.Parse() works var blURL, brURL = strings.Repeat("-", 50), strings.Repeat("_", 50) func bEncode(s string) string { s = strings.Replace(s, "{", blURL, -1) s = strings.Replace(s, "}", brURL, -1) return s } func bDecode(s string) string { s = strings.Replace(s, blURL, "{", -1) s = strings.Replace(s, brURL, "}", -1) return s } func permutePart(part0 string, srv *openapi3.Server) []string { type mapAndSlice struct { m map[string]struct{} s []string } var2val := make(map[string]mapAndSlice) max := 0 for name0, v := range srv.Variables { name := "{" + name0 + "}" if !strings.Contains(part0, name) { continue } m := map[string]struct{}{v.Default: {}} for _, value := range v.Enum { m[value] = struct{}{} } if l := len(m); l > max { max = l } s := make([]string, 0, len(m)) for value := range m { s = append(s, value) } var2val[name] = mapAndSlice{m: m, s: s} } if len(var2val) == 0 { return []string{part0} } partsMap := make(map[string]struct{}, max*len(var2val)) for i := 0; i < max; i++ { part := part0 for name, mas := range var2val { part = strings.Replace(part, name, mas.s[i%len(mas.s)], -1) } partsMap[part] = struct{}{} } parts := make([]string, 0, len(partsMap)) for part := range partsMap { parts = append(parts, part) } sort.Strings(parts) return parts } kin-openapi-0.124.0/routers/gorillamux/router_test.go000066400000000000000000000361101460422374200226710ustar00rootroot00000000000000package gorillamux import ( "context" "net/http" "sort" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/getkin/kin-openapi/openapi3" "github.com/getkin/kin-openapi/routers" ) func TestRouter(t *testing.T) { helloCONNECT := &openapi3.Operation{Responses: openapi3.NewResponses()} helloDELETE := &openapi3.Operation{Responses: openapi3.NewResponses()} helloGET := &openapi3.Operation{Responses: openapi3.NewResponses()} helloHEAD := &openapi3.Operation{Responses: openapi3.NewResponses()} helloOPTIONS := &openapi3.Operation{Responses: openapi3.NewResponses()} helloPATCH := &openapi3.Operation{Responses: openapi3.NewResponses()} helloPOST := &openapi3.Operation{Responses: openapi3.NewResponses()} helloPUT := &openapi3.Operation{Responses: openapi3.NewResponses()} helloTRACE := &openapi3.Operation{Responses: openapi3.NewResponses()} paramsGET := &openapi3.Operation{Responses: openapi3.NewResponses()} booksPOST := &openapi3.Operation{Responses: openapi3.NewResponses()} partialGET := &openapi3.Operation{Responses: openapi3.NewResponses()} doc := &openapi3.T{ OpenAPI: "3.0.0", Info: &openapi3.Info{ Title: "MyAPI", Version: "0.1", }, Paths: openapi3.NewPaths( openapi3.WithPath("/hello", &openapi3.PathItem{ Connect: helloCONNECT, Delete: helloDELETE, Get: helloGET, Head: helloHEAD, Options: helloOPTIONS, Patch: helloPATCH, Post: helloPOST, Put: helloPUT, Trace: helloTRACE, }), openapi3.WithPath("/onlyGET", &openapi3.PathItem{ Get: helloGET, }), openapi3.WithPath("/params/{x}/{y}/{z:.*}", &openapi3.PathItem{ Get: paramsGET, Parameters: openapi3.Parameters{ &openapi3.ParameterRef{Value: openapi3.NewPathParameter("x").WithSchema(openapi3.NewStringSchema())}, &openapi3.ParameterRef{Value: openapi3.NewPathParameter("y").WithSchema(openapi3.NewFloat64Schema())}, &openapi3.ParameterRef{Value: openapi3.NewPathParameter("z").WithSchema(openapi3.NewIntegerSchema())}, }, }), openapi3.WithPath("/books/{bookid}", &openapi3.PathItem{ Get: paramsGET, Parameters: openapi3.Parameters{ &openapi3.ParameterRef{Value: openapi3.NewPathParameter("bookid").WithSchema(openapi3.NewStringSchema())}, }, }), openapi3.WithPath("/books/{bookid}.json", &openapi3.PathItem{ Post: booksPOST, Parameters: openapi3.Parameters{ &openapi3.ParameterRef{Value: openapi3.NewPathParameter("bookid2").WithSchema(openapi3.NewStringSchema())}, }, }), openapi3.WithPath("/partial", &openapi3.PathItem{ Get: partialGET, }), ), } expect := func(r routers.Router, method string, uri string, operation *openapi3.Operation, params map[string]string) { t.Helper() req, err := http.NewRequest(method, uri, nil) require.NoError(t, err) route, pathParams, err := r.FindRoute(req) if err != nil { if operation == nil { pathItem := doc.Paths.Value(uri) if pathItem == nil { if err.Error() != routers.ErrPathNotFound.Error() { t.Fatalf("'%s %s': should have returned %q, but it returned an error: %v", method, uri, routers.ErrPathNotFound, err) } return } if pathItem.GetOperation(method) == nil { if err.Error() != routers.ErrMethodNotAllowed.Error() { t.Fatalf("'%s %s': should have returned %q, but it returned an error: %v", method, uri, routers.ErrMethodNotAllowed, err) } } } else { t.Fatalf("'%s %s': should have returned an operation, but it returned an error: %v", method, uri, err) } } if operation == nil && err == nil { t.Fatalf("'%s %s': should have failed, but returned\nroute = %+v\npathParams = %+v", method, uri, route, pathParams) } if route == nil { return } if route.Operation != operation { t.Fatalf("'%s %s': Returned wrong operation (%v)", method, uri, route.Operation) } if len(params) == 0 { if len(pathParams) != 0 { t.Fatalf("'%s %s': should return no path arguments, but found %+v", method, uri, pathParams) } } else { names := make([]string, 0, len(params)) for name := range params { names = append(names, name) } sort.Strings(names) for _, name := range names { expected := params[name] actual, exists := pathParams[name] if !exists { t.Fatalf("'%s %s': path parameter %q should be %q, but it's not defined.", method, uri, name, expected) } if actual != expected { t.Fatalf("'%s %s': path parameter %q should be %q, but it's %q", method, uri, name, expected, actual) } } } } err := doc.Validate(context.Background()) require.NoError(t, err) r, err := NewRouter(doc) require.NoError(t, err) expect(r, http.MethodGet, "/not_existing", nil, nil) expect(r, http.MethodDelete, "/hello", helloDELETE, nil) expect(r, http.MethodGet, "/hello", helloGET, nil) expect(r, http.MethodHead, "/hello", helloHEAD, nil) expect(r, http.MethodPatch, "/hello", helloPATCH, nil) expect(r, http.MethodPost, "/hello", helloPOST, nil) expect(r, http.MethodPut, "/hello", helloPUT, nil) expect(r, http.MethodGet, "/params/a/b/", paramsGET, map[string]string{ "x": "a", "y": "b", "z": "", }) expect(r, http.MethodGet, "/params/a/b/c%2Fd", paramsGET, map[string]string{ "x": "a", "y": "b", "z": "c%2Fd", }) expect(r, http.MethodGet, "/books/War.and.Peace", paramsGET, map[string]string{ "bookid": "War.and.Peace", }) expect(r, http.MethodPost, "/books/War.and.Peace.json", booksPOST, map[string]string{ "bookid": "War.and.Peace", }) expect(r, http.MethodPost, "/partial", nil, nil) doc.Servers = []*openapi3.Server{ {URL: "https://www.example.com/api/v1"}, {URL: "{scheme}://{d0}.{d1}.com/api/v1/", Variables: map[string]*openapi3.ServerVariable{ "d0": {Default: "www"}, "d1": {Default: "example", Enum: []string{"example"}}, "scheme": {Default: "https", Enum: []string{"https", "http"}}, }}, {URL: "http://127.0.0.1:{port}/api/v1", Variables: map[string]*openapi3.ServerVariable{ "port": {Default: "8000"}, }}, } err = doc.Validate(context.Background()) require.NoError(t, err) r, err = NewRouter(doc) require.NoError(t, err) expect(r, http.MethodGet, "/hello", nil, nil) expect(r, http.MethodGet, "/api/v1/hello", nil, nil) expect(r, http.MethodGet, "www.example.com/api/v1/hello", nil, nil) expect(r, http.MethodGet, "https:///api/v1/hello", nil, nil) expect(r, http.MethodGet, "https://www.example.com/hello", nil, nil) expect(r, http.MethodGet, "https://www.example.com/api/v1/hello", helloGET, nil) expect(r, http.MethodGet, "https://domain0.domain1.com/api/v1/hello", helloGET, map[string]string{ "d0": "domain0", "d1": "domain1", // "scheme": "https", TODO: https://github.com/gorilla/mux/issues/624 }) expect(r, http.MethodGet, "http://127.0.0.1:8000/api/v1/hello", helloGET, map[string]string{ "port": "8000", }) doc.Servers = []*openapi3.Server{ {URL: "{server}", Variables: map[string]*openapi3.ServerVariable{ "server": {Default: "/api/v1"}, }}, } err = doc.Validate(context.Background()) require.NoError(t, err) r, err = NewRouter(doc) require.NoError(t, err) expect(r, http.MethodGet, "https://myserver/api/v1/hello", helloGET, nil) { uri := "https://www.example.com/api/v1/onlyGET" expect(r, http.MethodGet, uri, helloGET, nil) req, err := http.NewRequest(http.MethodDelete, uri, nil) require.NoError(t, err) require.NotNil(t, req) route, pathParams, err := r.FindRoute(req) require.EqualError(t, err, routers.ErrMethodNotAllowed.Error()) require.Nil(t, route) require.Nil(t, pathParams) } } func TestPermuteScheme(t *testing.T) { scheme0 := "{sche}{me}" server := &openapi3.Server{URL: scheme0 + "://{d0}.{d1}.com/api/v1/", Variables: map[string]*openapi3.ServerVariable{ "d0": {Default: "www"}, "d1": {Default: "example", Enum: []string{"example"}}, "sche": {Default: "http"}, "me": {Default: "s", Enum: []string{"", "s"}}, }} err := server.Validate(context.Background()) require.NoError(t, err) perms := permutePart(scheme0, server) require.Equal(t, []string{"http", "https"}, perms) } func TestServerPath(t *testing.T) { server := &openapi3.Server{URL: "http://example.com"} err := server.Validate(context.Background()) require.NoError(t, err) _, err = NewRouter(&openapi3.T{Servers: openapi3.Servers{ server, &openapi3.Server{URL: "http://example.com/"}, &openapi3.Server{URL: "http://example.com/path"}, newServerWithVariables( "{scheme}://localhost", map[string]string{ "scheme": "https", }), newServerWithVariables( "{url}", map[string]string{ "url": "http://example.com/path", }), newServerWithVariables( "http://example.com:{port}/path", map[string]string{ "port": "8088", }), newServerWithVariables( "{server}", map[string]string{ "server": "/", }), newServerWithVariables( "/", nil, ), }, Paths: openapi3.NewPaths(), }) require.NoError(t, err) } func TestServerOverrideAtPathLevel(t *testing.T) { helloGET := &openapi3.Operation{Responses: openapi3.NewResponses()} doc := &openapi3.T{ OpenAPI: "3.0.0", Info: &openapi3.Info{ Title: "rel", Version: "1", }, Servers: openapi3.Servers{ &openapi3.Server{ URL: "https://example.com", }, }, Paths: openapi3.NewPaths( openapi3.WithPath("/hello", &openapi3.PathItem{ Servers: openapi3.Servers{ &openapi3.Server{ URL: "https://another.com", }, }, Get: helloGET, }), ), } err := doc.Validate(context.Background()) require.NoError(t, err) router, err := NewRouter(doc) require.NoError(t, err) req, err := http.NewRequest(http.MethodGet, "https://another.com/hello", nil) require.NoError(t, err) route, _, err := router.FindRoute(req) require.Equal(t, "/hello", route.Path) req, err = http.NewRequest(http.MethodGet, "https://example.com/hello", nil) require.NoError(t, err) route, _, err = router.FindRoute(req) require.Nil(t, route) require.Error(t, err) } func TestRelativeURL(t *testing.T) { helloGET := &openapi3.Operation{Responses: openapi3.NewResponses()} doc := &openapi3.T{ OpenAPI: "3.0.0", Info: &openapi3.Info{ Title: "rel", Version: "1", }, Servers: openapi3.Servers{ &openapi3.Server{ URL: "/api/v1", }, }, Paths: openapi3.NewPaths( openapi3.WithPath("/hello", &openapi3.PathItem{ Get: helloGET, }), ), } err := doc.Validate(context.Background()) require.NoError(t, err) router, err := NewRouter(doc) require.NoError(t, err) req, err := http.NewRequest(http.MethodGet, "https://example.com/api/v1/hello", nil) require.NoError(t, err) route, _, err := router.FindRoute(req) require.NoError(t, err) require.Equal(t, "/hello", route.Path) } func Test_makeServers(t *testing.T) { type testStruct struct { name string servers openapi3.Servers want []srv wantErr bool initFn func(tt *testStruct) } tests := []testStruct{ { name: "server is root path", servers: openapi3.Servers{ newServerWithVariables("/", nil), }, want: []srv{{ schemes: nil, host: "", base: "", server: nil, varsUpdater: nil, }}, wantErr: false, initFn: func(tt *testStruct) { for i, server := range tt.servers { tt.want[i].server = server } }, }, { name: "server with single variable that evaluates to root path", servers: openapi3.Servers{ newServerWithVariables("{server}", map[string]string{"server": "/"}), }, want: []srv{{ schemes: nil, host: "", base: "", server: nil, varsUpdater: nil, }}, wantErr: false, initFn: func(tt *testStruct) { for i, server := range tt.servers { tt.want[i].server = server } }, }, { name: "server is http://localhost:28002", servers: openapi3.Servers{ newServerWithVariables("http://localhost:28002", nil), }, want: []srv{{ schemes: []string{"http"}, host: "localhost:28002", base: "", server: nil, varsUpdater: nil, }}, wantErr: false, initFn: func(tt *testStruct) { for i, server := range tt.servers { tt.want[i].server = server } }, }, { name: "server with single variable that evaluates to http://localhost:28002", servers: openapi3.Servers{ newServerWithVariables("{server}", map[string]string{"server": "http://localhost:28002"}), }, want: []srv{{ schemes: []string{"http"}, host: "localhost:28002", base: "", server: nil, varsUpdater: nil, }}, wantErr: false, initFn: func(tt *testStruct) { for i, server := range tt.servers { tt.want[i].server = server } }, }, { name: "server with multiple variables that evaluates to http://localhost:28002", servers: openapi3.Servers{ newServerWithVariables("{scheme}://{host}:{port}", map[string]string{"scheme": "http", "host": "localhost", "port": "28002"}), }, want: []srv{{ schemes: []string{"http"}, host: "{host}:28002", base: "", server: nil, varsUpdater: func(vars map[string]string) { vars["port"] = "28002" }, }}, wantErr: false, initFn: func(tt *testStruct) { for i, server := range tt.servers { tt.want[i].server = server } }, }, { name: "server with unparsable URL fails", servers: openapi3.Servers{ newServerWithVariables("exam^ple.com:443", nil), }, want: nil, wantErr: true, initFn: nil, }, { name: "server with single variable that evaluates to unparsable URL fails", servers: openapi3.Servers{ newServerWithVariables("{server}", map[string]string{"server": "exam^ple.com:443"}), }, want: nil, wantErr: true, initFn: nil, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.initFn != nil { tt.initFn(&tt) } got, err := makeServers(tt.servers) if (err != nil) != tt.wantErr { t.Errorf("makeServers() error = %v, wantErr %v", err, tt.wantErr) return } assert.Equal(t, len(tt.want), len(got), "expected and actual servers lengths are not equal") for i := 0; i < len(tt.want); i++ { // Unfortunately using assert.Equals or reflect.DeepEquals isn't // an option because function pointers cannot be compared assert.Equal(t, tt.want[i].schemes, got[i].schemes) assert.Equal(t, tt.want[i].host, got[i].host) assert.Equal(t, tt.want[i].host, got[i].host) assert.Equal(t, tt.want[i].server, got[i].server) if tt.want[i].varsUpdater == nil { assert.Nil(t, got[i].varsUpdater, "expected and actual varsUpdater should point to same function") } else { assert.NotNil(t, got[i].varsUpdater, "expected and actual varsUpdater should point to same function") } } }) } } func newServerWithVariables(url string, variables map[string]string) *openapi3.Server { var serverVariables = map[string]*openapi3.ServerVariable{} for key, value := range variables { serverVariables[key] = newServerVariable(value) } return &openapi3.Server{ URL: url, Description: "", Variables: serverVariables, } } func newServerVariable(defaultValue string) *openapi3.ServerVariable { return &openapi3.ServerVariable{ Enum: nil, Default: defaultValue, Description: "", } } kin-openapi-0.124.0/routers/issue356_test.go000066400000000000000000000074331460422374200205620ustar00rootroot00000000000000package routers_test import ( "context" "io" "net/http" "net/http/httptest" "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" "github.com/getkin/kin-openapi/routers/gorillamux" "github.com/getkin/kin-openapi/routers/legacy" ) func TestIssue356(t *testing.T) { spec := func(servers string) []byte { return []byte(` openapi: 3.0.0 info: title: Example version: '1.0' description: test servers: ` + servers + ` paths: /test: post: responses: '201': description: Created content: application/json: schema: {type: object} requestBody: content: application/json: schema: {type: object} description: '' description: Create a test object `) } for servers, expectError := range map[string]bool{ ` - url: http://localhost:3000/base - url: /base `: false, ` - url: /base - url: http://localhost:3000/base `: false, `- url: /base`: false, `- url: http://localhost:3000/base`: true, ``: true, } { loader := &openapi3.Loader{Context: context.Background()} t.Logf("using servers: %q (%v)", servers, expectError) doc, err := loader.LoadFromData(spec(servers)) require.NoError(t, err) err = doc.Validate(context.Background()) require.NoError(t, err) gorillamuxNewRouterWrapped := func(doc *openapi3.T, opts ...openapi3.ValidationOption) (routers.Router, error) { return gorillamux.NewRouter(doc) } for i, newRouter := range []func(*openapi3.T, ...openapi3.ValidationOption) (routers.Router, error){gorillamuxNewRouterWrapped, legacy.NewRouter} { t.Logf("using NewRouter from %s", map[int]string{0: "gorillamux", 1: "legacy"}[i]) router, err := newRouter(doc) require.NoError(t, err) if true { t.Logf("using naked newRouter") httpReq, err := http.NewRequest(http.MethodPost, "/base/test", strings.NewReader(`{}`)) require.NoError(t, err) httpReq.Header.Set("Content-Type", "application/json") route, pathParams, err := router.FindRoute(httpReq) if expectError { require.Error(t, err, routers.ErrPathNotFound) return } require.NoError(t, err) requestValidationInput := &openapi3filter.RequestValidationInput{ Request: httpReq, PathParams: pathParams, Route: route, } err = openapi3filter.ValidateRequest(context.Background(), requestValidationInput) require.NoError(t, err) } if true { t.Logf("using httptest.NewServer") ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { route, pathParams, err := router.FindRoute(r) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) return } requestValidationInput := &openapi3filter.RequestValidationInput{ Request: r, PathParams: pathParams, Route: route, } err = openapi3filter.ValidateRequest(r.Context(), requestValidationInput) require.NoError(t, err) w.Header().Set("Content-Type", "application/json") w.Write([]byte("{}")) })) defer ts.Close() req, err := http.NewRequest(http.MethodPost, ts.URL+"/base/test", strings.NewReader(`{}`)) require.NoError(t, err) req.Header.Set("Content-Type", "application/json") rep, err := http.DefaultClient.Do(req) require.NoError(t, err) defer rep.Body.Close() body, err := io.ReadAll(rep.Body) require.NoError(t, err) if expectError { require.Equal(t, 500, rep.StatusCode) require.Equal(t, routers.ErrPathNotFound.Error(), string(body)) return } require.Equal(t, 200, rep.StatusCode) require.Equal(t, "{}", string(body)) } } } } kin-openapi-0.124.0/routers/legacy/000077500000000000000000000000001460422374200170435ustar00rootroot00000000000000kin-openapi-0.124.0/routers/legacy/issue444_test.go000066400000000000000000000025021460422374200220140ustar00rootroot00000000000000package legacy_test import ( "bytes" "context" "net/http/httptest" "testing" "github.com/stretchr/testify/require" "github.com/getkin/kin-openapi/openapi3" "github.com/getkin/kin-openapi/openapi3filter" legacyrouter "github.com/getkin/kin-openapi/routers/legacy" ) func TestIssue444(t *testing.T) { loader := openapi3.NewLoader() oas, err := loader.LoadFromData([]byte(` openapi: '3.0.0' info: title: API version: 1.0.0 paths: '/path': post: requestBody: required: true content: application/x-yaml: schema: type: object responses: '200': description: x content: application/json: schema: type: string `)) require.NoError(t, err) router, err := legacyrouter.NewRouter(oas) require.NoError(t, err) r := httptest.NewRequest("POST", "/path", bytes.NewReader([]byte(` foo: bar `))) r.Header.Set("Content-Type", "application/x-yaml") openapi3.SchemaErrorDetailsDisabled = true route, pathParams, err := router.FindRoute(r) require.NoError(t, err) reqValidationInput := &openapi3filter.RequestValidationInput{ Request: r, PathParams: pathParams, Route: route, } err = openapi3filter.ValidateRequest(context.Background(), reqValidationInput) require.NoError(t, err) } kin-openapi-0.124.0/routers/legacy/pathpattern/000077500000000000000000000000001460422374200213755ustar00rootroot00000000000000kin-openapi-0.124.0/routers/legacy/pathpattern/node.go000066400000000000000000000201211460422374200226450ustar00rootroot00000000000000// 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) package pathpattern import ( "bytes" "fmt" "regexp" "sort" "strings" ) var DefaultOptions = &Options{ SupportWildcard: true, } type Options struct { SupportWildcard bool SupportRegExp bool } // 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" func PathFromHost(host string, specialDashes bool) string { buf := make([]byte, 0, len(host)) end := len(host) // Go from end to start for start := end - 1; start >= 0; start-- { switch host[start] { case '.': buf = append(buf, host[start+1:end]...) buf = append(buf, '/', '.', '/') end = start case '-': if specialDashes { buf = append(buf, host[start+1:end]...) buf = append(buf, '/', '-', '/') end = start } } } buf = append(buf, host[:end]...) return string(buf) } type Node struct { VariableNames []string Value interface{} Suffixes SuffixList } func (currentNode *Node) String() string { buf := bytes.NewBuffer(make([]byte, 0, 255)) currentNode.toBuffer(buf, "") return buf.String() } func (currentNode *Node) toBuffer(buf *bytes.Buffer, linePrefix string) { if value := currentNode.Value; value != nil { buf.WriteString(linePrefix) buf.WriteString("VALUE: ") fmt.Fprint(buf, value) buf.WriteString("\n") } suffixes := currentNode.Suffixes if len(suffixes) > 0 { newLinePrefix := linePrefix + " " for _, suffix := range suffixes { buf.WriteString(linePrefix) buf.WriteString("PATTERN: ") buf.WriteString(suffix.String()) buf.WriteString("\n") suffix.Node.toBuffer(buf, newLinePrefix) } } } type SuffixKind int // Note that order is important! 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 ) // Suffix describes condition that type Suffix struct { Kind SuffixKind Pattern string // compiled regular expression regExp *regexp.Regexp // Next node Node *Node } func EqualSuffix(a, b Suffix) bool { return a.Kind == b.Kind && a.Pattern == b.Pattern } func (suffix Suffix) String() string { switch suffix.Kind { case SuffixKindConstant: return suffix.Pattern case SuffixKindVariable: return "{_}" case SuffixKindEverything: return "{_*}" default: return "{_|" + suffix.Pattern + "}" } } type SuffixList []Suffix func (list SuffixList) Less(i, j int) bool { a, b := list[i], list[j] ak, bk := a.Kind, b.Kind if ak < bk { return true } else if bk < ak { return false } return a.Pattern > b.Pattern } func (list SuffixList) Len() int { return len(list) } func (list SuffixList) Swap(i, j int) { a, b := list[i], list[j] list[i], list[j] = b, a } func (currentNode *Node) MustAdd(path string, value interface{}, options *Options) { node, err := currentNode.CreateNode(path, options) if err != nil { panic(err) } node.Value = value } func (currentNode *Node) Add(path string, value interface{}, options *Options) error { node, err := currentNode.CreateNode(path, options) if err != nil { return err } node.Value = value return nil } func (currentNode *Node) CreateNode(path string, options *Options) (*Node, error) { if options == nil { options = DefaultOptions } for strings.HasSuffix(path, "/") { path = path[:len(path)-1] } remaining := path var variableNames []string loop: for { //remaining = strings.TrimPrefix(remaining, "/") if len(remaining) == 0 { // This node is the right one // Check whether another route already leads to this node currentNode.VariableNames = variableNames return currentNode, nil } suffix := Suffix{} var i int if strings.HasPrefix(remaining, "/") { remaining = remaining[1:] suffix.Kind = SuffixKindConstant suffix.Pattern = "/" } else { i = strings.IndexAny(remaining, "/{") if i < 0 { i = len(remaining) } if i > 0 { // Constant string pattern suffix.Kind = SuffixKindConstant suffix.Pattern = remaining[:i] remaining = remaining[i:] } else if remaining[0] == '{' { // This is probably a variable suffix.Kind = SuffixKindVariable // Find variable name i := strings.IndexByte(remaining, '}') if i < 0 { return nil, fmt.Errorf("missing '}' in: %s", path) } variableName := strings.TrimSpace(remaining[1:i]) remaining = remaining[i+1:] if options.SupportRegExp { // See if it has regular expression i = strings.IndexByte(variableName, '|') if i >= 0 { suffix.Kind = SuffixKindRegExp suffix.Pattern = strings.TrimSpace(variableName[i+1:]) variableName = strings.TrimSpace(variableName[:i]) } } if suffix.Kind == SuffixKindVariable && options.SupportWildcard { if strings.HasSuffix(variableName, "*") { suffix.Kind = SuffixKindEverything } } variableNames = append(variableNames, variableName) } } // Find existing matcher for _, existing := range currentNode.Suffixes { if EqualSuffix(existing, suffix) { currentNode = existing.Node continue loop } } // Compile regular expression if suffix.Kind == SuffixKindRegExp { regExp, err := regexp.Compile(suffix.Pattern) if err != nil { return nil, fmt.Errorf("invalid regular expression in: %s", path) } suffix.regExp = regExp } // Create new node newNode := &Node{} suffix.Node = newNode currentNode.Suffixes = append(currentNode.Suffixes, suffix) sort.Sort(currentNode.Suffixes) currentNode = newNode continue loop } } func (currentNode *Node) Match(path string) (*Node, []string) { for strings.HasSuffix(path, "/") { path = path[:len(path)-1] } variableValues := make([]string, 0, 8) return currentNode.matchRemaining(path, variableValues) } func (currentNode *Node) matchRemaining(remaining string, paramValues []string) (*Node, []string) { // Check if this node matches if len(remaining) == 0 && currentNode.Value != nil { return currentNode, paramValues } // See if any suffix matches for _, suffix := range currentNode.Suffixes { var resultNode *Node var resultValues []string switch suffix.Kind { case SuffixKindConstant: pattern := suffix.Pattern if strings.HasPrefix(remaining, pattern) { newRemaining := remaining[len(pattern):] resultNode, resultValues = suffix.Node.matchRemaining(newRemaining, paramValues) } else if len(remaining) == 0 && pattern == "/" { resultNode, resultValues = suffix.Node.matchRemaining(remaining, paramValues) } case SuffixKindVariable: i := strings.IndexByte(remaining, '/') if i < 0 { i = len(remaining) } newParamValues := append(paramValues, remaining[:i]) newRemaining := remaining[i:] resultNode, resultValues = suffix.Node.matchRemaining(newRemaining, newParamValues) case SuffixKindEverything: newParamValues := append(paramValues, remaining) resultNode, resultValues = suffix.Node, newParamValues case SuffixKindRegExp: i := strings.IndexByte(remaining, '/') if i < 0 { i = len(remaining) } paramValue := remaining[:i] regExp := suffix.regExp if regExp.MatchString(paramValue) { matches := regExp.FindStringSubmatch(paramValue) if len(matches) > 1 { paramValue = matches[1] } newParamValues := append(paramValues, paramValue) newRemaining := remaining[i:] resultNode, resultValues = suffix.Node.matchRemaining(newRemaining, newParamValues) } } if resultNode != nil && resultNode.Value != nil { // This suffix matched return resultNode, resultValues } } // No suffix matched return nil, nil } kin-openapi-0.124.0/routers/legacy/pathpattern/node_test.go000066400000000000000000000045041460422374200237130ustar00rootroot00000000000000package pathpattern import ( "testing" ) func TestPatterns(t *testing.T) { DefaultOptions.SupportRegExp = true rootNode := &Node{} add := func(path, value string) { rootNode.MustAdd(path, value, nil) } add("GET /abc", "GET METHOD") add("POST /abc", "POST METHOD") add("/abc", "SIMPLE") add("/abc/fixedString", "FIXED STRING") add("/abc/{param}", "FILE") add("/abc/{param*}", "DEEP FILE") add("/abc/{fileName|(.*)\\.jpeg}", "JPEG") add("/abc/{fileName|some_prefix_(.*)\\.jpeg}", "PREFIXED JPEG") add("/root/{path*}", "DIRECTORY") add("/impossible_route", "IMPOSSIBLE") add(PathFromHost("www.nike.com", true), "WWW-HOST") add(PathFromHost("{other}.nike.com", true), "OTHER-HOST") expect := func(uri string, expected string, expectedArgs ...string) { actually := "not found" node, actualArgs := rootNode.Match(uri) if node != nil { if s, ok := node.Value.(string); ok { actually = s } } if actually != expected { t.Fatalf("Wrong path!\nInput: %s\nExpected: %q\nActually: %q\nTree:\n%s\n\n", uri, expected, actually, rootNode.String()) return } if !argsEqual(expectedArgs, actualArgs) { t.Fatalf("Wrong variable values!\nInput: %s\nExpected: %q\nActually: %q\nTree:\n%s\n\n", uri, expectedArgs, actualArgs, rootNode.String()) return } } expect("", "not found") expect("/", "not found") expect("GET /abc", "GET METHOD") expect("GET /abc/", "GET METHOD") expect("POST /abc", "POST METHOD") expect("/url_without_handler", "not found") expect("/abc", "SIMPLE") expect("/abc/fixedString", "FIXED STRING") expect("/abc/09az", "FILE", "09az") expect("/abc/09az/1/2/3", "DEEP FILE", "09az/1/2/3") expect("/abc/09az/1/2/3/", "DEEP FILE", "09az/1/2/3") expect("/abc/someFile.jpeg", "JPEG", "someFile") expect("/abc/someFile.old.jpeg", "JPEG", "someFile.old") expect("/abc/some_prefix_someFile.jpeg", "PREFIXED JPEG", "someFile") expect("/root", "DIRECTORY", "") expect("/root/", "DIRECTORY", "") expect("/root/a/b/c", "DIRECTORY", "a/b/c") expect(PathFromHost("www.nike.com", true), "WWW-HOST") expect(PathFromHost("example.nike.com", true), "OTHER-HOST", "example") expect(PathFromHost("subdomain.example.nike.com", true), "not found") } func argsEqual(a, b []string) bool { if len(a) != len(b) { return false } for i, ai := range a { if ai != b[i] { return false } } return true } kin-openapi-0.124.0/routers/legacy/router.go000066400000000000000000000106171460422374200207170ustar00rootroot00000000000000// 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.*}) package legacy import ( "context" "errors" "fmt" "net/http" "strings" "github.com/getkin/kin-openapi/openapi3" "github.com/getkin/kin-openapi/routers" "github.com/getkin/kin-openapi/routers/legacy/pathpattern" ) // Routers maps a HTTP request to a Router. type Routers []*Router // FindRoute extracts the route and parameters of an http.Request func (rs Routers) FindRoute(req *http.Request) (routers.Router, *routers.Route, map[string]string, error) { for _, router := range rs { // Skip routers that have DO NOT have servers if len(router.doc.Servers) == 0 { continue } route, pathParams, err := router.FindRoute(req) if err == nil { return router, route, pathParams, nil } } for _, router := range rs { // Skip routers that DO have servers if len(router.doc.Servers) > 0 { continue } route, pathParams, err := router.FindRoute(req) if err == nil { return router, route, pathParams, nil } } return nil, nil, nil, &routers.RouteError{ Reason: "none of the routers match", } } // Router maps a HTTP request to an OpenAPI operation. type Router struct { doc *openapi3.T pathNode *pathpattern.Node } // 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. func NewRouter(doc *openapi3.T, opts ...openapi3.ValidationOption) (routers.Router, error) { if err := doc.Validate(context.Background(), opts...); err != nil { return nil, fmt.Errorf("validating OpenAPI failed: %w", err) } router := &Router{doc: doc} root := router.node() for path, pathItem := range doc.Paths.Map() { for method, operation := range pathItem.Operations() { method = strings.ToUpper(method) if err := root.Add(method+" "+path, &routers.Route{ Spec: doc, Path: path, PathItem: pathItem, Method: method, Operation: operation, }, nil); err != nil { return nil, err } } } return router, nil } // AddRoute adds a route in the router. func (router *Router) AddRoute(route *routers.Route) error { method := route.Method if method == "" { return errors.New("route is missing method") } method = strings.ToUpper(method) path := route.Path if path == "" { return errors.New("route is missing path") } return router.node().Add(method+" "+path, router, nil) } func (router *Router) node() *pathpattern.Node { root := router.pathNode if root == nil { root = &pathpattern.Node{} router.pathNode = root } return root } // FindRoute extracts the route and parameters of an http.Request func (router *Router) FindRoute(req *http.Request) (*routers.Route, map[string]string, error) { method, url := req.Method, req.URL doc := router.doc // Get server servers := doc.Servers var server *openapi3.Server var remainingPath string var pathParams map[string]string if len(servers) == 0 { remainingPath = url.Path } else { var paramValues []string server, paramValues, remainingPath = servers.MatchURL(url) if server == nil { return nil, nil, &routers.RouteError{ Reason: routers.ErrPathNotFound.Error(), } } pathParams = make(map[string]string) paramNames, err := server.ParameterNames() if err != nil { return nil, nil, err } for i, value := range paramValues { name := paramNames[i] pathParams[name] = value } } // Get PathItem root := router.node() var route *routers.Route node, paramValues := root.Match(method + " " + remainingPath) if node != nil { route, _ = node.Value.(*routers.Route) } if route == nil { pathItem := doc.Paths.Value(remainingPath) if pathItem == nil { return nil, nil, &routers.RouteError{Reason: routers.ErrPathNotFound.Error()} } if pathItem.GetOperation(method) == nil { return nil, nil, &routers.RouteError{Reason: routers.ErrMethodNotAllowed.Error()} } } if pathParams == nil { pathParams = make(map[string]string, len(paramValues)) } paramKeys := node.VariableNames for i, value := range paramValues { key := strings.TrimSuffix(paramKeys[i], "*") pathParams[key] = value } return route, pathParams, nil } kin-openapi-0.124.0/routers/legacy/router_test.go000066400000000000000000000164731460422374200217640ustar00rootroot00000000000000package legacy import ( "context" "net/http" "sort" "testing" "github.com/stretchr/testify/require" "github.com/getkin/kin-openapi/openapi3" "github.com/getkin/kin-openapi/routers" ) func TestRouter(t *testing.T) { helloCONNECT := &openapi3.Operation{Responses: openapi3.NewResponses()} helloDELETE := &openapi3.Operation{Responses: openapi3.NewResponses()} helloGET := &openapi3.Operation{Responses: openapi3.NewResponses()} helloHEAD := &openapi3.Operation{Responses: openapi3.NewResponses()} helloOPTIONS := &openapi3.Operation{Responses: openapi3.NewResponses()} helloPATCH := &openapi3.Operation{Responses: openapi3.NewResponses()} helloPOST := &openapi3.Operation{Responses: openapi3.NewResponses()} helloPUT := &openapi3.Operation{Responses: openapi3.NewResponses()} helloTRACE := &openapi3.Operation{Responses: openapi3.NewResponses()} paramsGET := &openapi3.Operation{Responses: openapi3.NewResponses()} booksPOST := &openapi3.Operation{Responses: openapi3.NewResponses()} partialGET := &openapi3.Operation{Responses: openapi3.NewResponses()} doc := &openapi3.T{ OpenAPI: "3.0.0", Info: &openapi3.Info{ Title: "MyAPI", Version: "0.1", }, Paths: openapi3.NewPaths( openapi3.WithPath("/hello", &openapi3.PathItem{ Connect: helloCONNECT, Delete: helloDELETE, Get: helloGET, Head: helloHEAD, Options: helloOPTIONS, Patch: helloPATCH, Post: helloPOST, Put: helloPUT, Trace: helloTRACE, }), openapi3.WithPath("/onlyGET", &openapi3.PathItem{ Get: helloGET, }), openapi3.WithPath("/params/{x}/{y}/{z.*}", &openapi3.PathItem{ Get: paramsGET, Parameters: openapi3.Parameters{ &openapi3.ParameterRef{Value: openapi3.NewPathParameter("x").WithSchema(openapi3.NewStringSchema())}, &openapi3.ParameterRef{Value: openapi3.NewPathParameter("y").WithSchema(openapi3.NewFloat64Schema())}, &openapi3.ParameterRef{Value: openapi3.NewPathParameter("z").WithSchema(openapi3.NewIntegerSchema())}, }, }), openapi3.WithPath("/books/{bookid}", &openapi3.PathItem{ Get: paramsGET, Parameters: openapi3.Parameters{ &openapi3.ParameterRef{Value: openapi3.NewPathParameter("bookid").WithSchema(openapi3.NewStringSchema())}, }, }), openapi3.WithPath("/books/{bookid2}.json", &openapi3.PathItem{ Post: booksPOST, Parameters: openapi3.Parameters{ &openapi3.ParameterRef{Value: openapi3.NewPathParameter("bookid2").WithSchema(openapi3.NewStringSchema())}, }, }), openapi3.WithPath("/partial", &openapi3.PathItem{ Get: partialGET, }), ), } expect := func(r routers.Router, method string, uri string, operation *openapi3.Operation, params map[string]string) { req, err := http.NewRequest(method, uri, nil) require.NoError(t, err) route, pathParams, err := r.FindRoute(req) if err != nil { if operation == nil { pathItem := doc.Paths.Value(uri) if pathItem == nil { if err.Error() != routers.ErrPathNotFound.Error() { t.Fatalf("'%s %s': should have returned %q, but it returned an error: %v", method, uri, routers.ErrPathNotFound, err) } return } if pathItem.GetOperation(method) == nil { if err.Error() != routers.ErrMethodNotAllowed.Error() { t.Fatalf("'%s %s': should have returned %q, but it returned an error: %v", method, uri, routers.ErrMethodNotAllowed, err) } } } else { t.Fatalf("'%s %s': should have returned an operation, but it returned an error: %v", method, uri, err) } } if operation == nil && err == nil { t.Fatalf("'%s %s': should have failed, but returned\nroute = %+v\npathParams = %+v", method, uri, route, pathParams) } if route == nil { return } if route.Operation != operation { t.Fatalf("'%s %s': Returned wrong operation (%v)", method, uri, route.Operation) } if len(params) == 0 { if len(pathParams) != 0 { t.Fatalf("'%s %s': should return no path arguments, but found %+v", method, uri, pathParams) } } else { names := make([]string, 0, len(params)) for name := range params { names = append(names, name) } sort.Strings(names) for _, name := range names { expected := params[name] actual, exists := pathParams[name] if !exists { t.Fatalf("'%s %s': path parameter %q should be %q, but it's not defined.", method, uri, name, expected) } if actual != expected { t.Fatalf("'%s %s': path parameter %q should be %q, but it's %q", method, uri, name, expected, actual) } } } } err := doc.Validate(context.Background()) require.NoError(t, err) r, err := NewRouter(doc) require.NoError(t, err) expect(r, http.MethodGet, "/not_existing", nil, nil) expect(r, http.MethodDelete, "/hello", helloDELETE, nil) expect(r, http.MethodGet, "/hello", helloGET, nil) expect(r, http.MethodHead, "/hello", helloHEAD, nil) expect(r, http.MethodPatch, "/hello", helloPATCH, nil) expect(r, http.MethodPost, "/hello", helloPOST, nil) expect(r, http.MethodPut, "/hello", helloPUT, nil) expect(r, http.MethodGet, "/params/a/b/", paramsGET, map[string]string{ "x": "a", "y": "b", // "z": "", }) expect(r, http.MethodGet, "/params/a/b/c%2Fd", paramsGET, map[string]string{ "x": "a", "y": "b", // "z": "c/d", }) expect(r, http.MethodGet, "/books/War.and.Peace", paramsGET, map[string]string{ "bookid": "War.and.Peace", }) { req, err := http.NewRequest(http.MethodPost, "/books/War.and.Peace.json", nil) require.NoError(t, err) _, _, err = r.FindRoute(req) require.EqualError(t, err, routers.ErrPathNotFound.Error()) } expect(r, http.MethodPost, "/partial", nil, nil) doc.Servers = []*openapi3.Server{ {URL: "https://www.example.com/api/v1"}, {URL: "https://{d0}.{d1}.com/api/v1/", Variables: map[string]*openapi3.ServerVariable{ "d0": {Default: "www"}, "d1": {Default: "example", Enum: []string{"example"}}, }}, } err = doc.Validate(context.Background()) require.NoError(t, err) r, err = NewRouter(doc) require.NoError(t, err) expect(r, http.MethodGet, "/hello", nil, nil) expect(r, http.MethodGet, "/api/v1/hello", nil, nil) expect(r, http.MethodGet, "www.example.com/api/v1/hello", nil, nil) expect(r, http.MethodGet, "https:///api/v1/hello", nil, nil) expect(r, http.MethodGet, "https://www.example.com/hello", nil, nil) expect(r, http.MethodGet, "https://www.example.com/api/v1/hello", helloGET, nil) expect(r, http.MethodGet, "https://domain0.domain1.com/api/v1/hello", helloGET, map[string]string{ "d0": "domain0", "d1": "domain1", }) { uri := "https://www.example.com/api/v1/onlyGET" expect(r, http.MethodGet, uri, helloGET, nil) req, err := http.NewRequest(http.MethodDelete, uri, nil) require.NoError(t, err) require.NotNil(t, req) route, pathParams, err := r.FindRoute(req) require.EqualError(t, err, routers.ErrMethodNotAllowed.Error()) require.Nil(t, route) require.Nil(t, pathParams) } schema := &openapi3.Schema{ Type: &openapi3.Types{"string"}, Example: 3, } content := openapi3.NewContentWithJSONSchema(schema) responses := openapi3.NewResponses() responses.Value("default").Value.Content = content doc.Paths.Set("/withExamples", &openapi3.PathItem{ Get: &openapi3.Operation{Responses: responses}, }) err = doc.Validate(context.Background()) require.Error(t, err) r, err = NewRouter(doc) require.Error(t, err) r, err = NewRouter(doc, openapi3.DisableExamplesValidation()) require.NoError(t, err) } kin-openapi-0.124.0/routers/legacy/validate_request_test.go000066400000000000000000000043361460422374200240000ustar00rootroot00000000000000package legacy_test import ( "bytes" "encoding/json" "fmt" "net/http" "github.com/getkin/kin-openapi/openapi3" "github.com/getkin/kin-openapi/openapi3filter" "github.com/getkin/kin-openapi/routers/legacy" ) const spec = ` openapi: 3.0.0 info: title: My API version: 0.0.1 paths: /: post: responses: default: description: '' requestBody: required: true content: application/json: schema: oneOf: - $ref: '#/components/schemas/Cat' - $ref: '#/components/schemas/Dog' discriminator: propertyName: pet_type components: schemas: Pet: type: object required: [pet_type] properties: pet_type: type: string discriminator: propertyName: pet_type Dog: allOf: - $ref: '#/components/schemas/Pet' - type: object properties: breed: type: string enum: [Dingo, Husky, Retriever, Shepherd] Cat: allOf: - $ref: '#/components/schemas/Pet' - type: object properties: hunts: type: boolean age: type: integer ` func Example() { 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 := legacy.NewRouter(doc) if err != nil { panic(err) } p, err := json.Marshal(map[string]interface{}{ "pet_type": "Cat", "breed": "Dingo", "bark": true, }) if err != nil { panic(err) } req, err := http.NewRequest(http.MethodPost, "/", bytes.NewReader(p)) if err != nil { panic(err) } req.Header.Set("Content-Type", "application/json") route, pathParams, err := router.FindRoute(req) if err != nil { panic(err) } requestValidationInput := &openapi3filter.RequestValidationInput{ Request: req, PathParams: pathParams, Route: route, } if err := openapi3filter.ValidateRequest(loader.Context, requestValidationInput); err != nil { fmt.Println(err) } // Output: // request body has an error: doesn't match schema: input matches more than one oneOf schemas } kin-openapi-0.124.0/routers/types.go000066400000000000000000000024161460422374200172750ustar00rootroot00000000000000package routers import ( "net/http" "github.com/getkin/kin-openapi/openapi3" ) // Router helps link http.Request.s and an OpenAPIv3 spec 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) } // Route describes the operation an http.Request can match type Route struct { Spec *openapi3.T Server *openapi3.Server Path string PathItem *openapi3.PathItem Method string Operation *openapi3.Operation } // ErrPathNotFound is returned when no route match is found var ErrPathNotFound error = &RouteError{"no matching operation was found"} // ErrMethodNotAllowed is returned when no method of the matched route matches var ErrMethodNotAllowed error = &RouteError{"method not allowed"} // RouteError describes Router errors type RouteError struct { Reason string } func (e *RouteError) Error() string { return e.Reason }