Integration-owned API clients
This increment supports the subset below for Swagger 2.0, OpenAPI 3.0 and OpenAPI 3.1 JSON descriptions. Supported methods are GET, POST, PUT, PATCH, DELETE, HEAD and OPTIONS. TRACE fails compilation because it is not supported by the device HTTP capability. Nested object parameter encodings, reserved expansion, binary bodies, remote references, OAuth flows, additional unsupported constraints and body/response amendments are deferred; unsupported selected contracts fail compilation. Standalone JSON Schema is not an API description, and SOAP/WSDL is a separate future project.
Package the API description and its API definition together in one named api-client
under api-clients. The client belongs to the integration version; it is not seeded
into the tenant external-schema registry and does not create a shared schema resource.
<integration id="com.example.people" version="1.0.0" sdk-version="2.0">
<resources><resource id="service" type="protocol/http"/></resources>
<api-clients>
<api-client name="people">
<schema><![CDATA[{"swagger":"2.0","produces":["application/json"],"paths":{"/people/{id}":{"get":{"operationId":"getPerson","parameters":[{"name":"id","in":"path","required":true,"type":"integer"}],"responses":{"200":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"}}}}}}}}}]]></schema>
<definition><![CDATA[<api-client name="people" schema-sha256="49a439854084d1c5f2beef5eb00249a6fd0dd54ce28a120963091f6340f264db"><operation id="getPerson"/></api-client>]]></definition>
</api-client>
</api-clients>
<actor><actions><action name="person" label="Read person"><source><![CDATA[
local client = ctx.api.get("people"):init(ctx.device.get("service"))
local ok, response = client:getPerson({path={id=1}})
if not ok then error(response) end
return response.body.name
]]></source></action></actions></actor>
</integration>schema holds the exact Swagger 2.0, OpenAPI 3.0 or OpenAPI 3.1 JSON bytes as XML text or CDATA. definition
holds the DataXML API contract, including selected operations, typed parameter overrides,
and any auth-provider source. Its name must match the enclosing client and its
schema-sha256 must hash the decoded schema text exactly. Changing whitespace in the
schema therefore requires updating the pin. The compiler supports scalar path/query/header parameters (including enums), JSON request bodies,
exact and default response statuses, OpenAPI status ranges and empty responses. Declared HTTP error responses
return typed status/body values; uncovered statuses produce a recoverable failure.
Exact statuses take precedence over ranges, which take precedence over default.
Each response keeps a distinct set of status literals so Lua can narrow its body by status.
Repeated generated response shapes share type declarations to keep exports compact.
Operation definitions can add, replace or remove typed path, query and header parameters
with <parameter location="..." name="..." action="..."/>. The GraphQL equivalent is
APIClientOperationInput.parameters. Add/replace requires type (string, integer,
number, boolean or array); arrays additionally require item-type (GraphQL
itemType). required controls presence. collection-format (GraphQL collectionFormat)
selects the existing device serialization: csv by default, or supported ssv, tsv,
pipes and multi modes. Location restrictions still apply.
Amendments operate on resolved parameters, including path-level inheritance and local
parameter references. Header matching ignores case. add fails if the parameter exists;
replace and remove fail if it does not. A remove has no type or serialization fields.
The earlier <query> / GraphQL queries form remains supported for scalar queries,
and duplicates across both forms are rejected. Path parameters must be required and
match an existing template placeholder. These declarations cannot change the route,
introduce a URL, store values, or take ownership of authentication/transport headers.
All changes remain pinned to the original schema hash and affect generated types before
scripts are checked. This example adds an undocumented header and repeated query array:
<integration id="com.example.parameters" version="1.0.0" sdk-version="2.0">
<resources><resource id="service" type="protocol/http"/></resources>
<api-clients>
<api-client name="people">
<schema><![CDATA[{"swagger":"2.0","produces":["application/json"],"paths":{"/people/{id}":{"get":{"operationId":"getPerson","parameters":[{"name":"id","in":"path","required":true,"type":"integer"}],"responses":{"200":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"}}}}}}}}}]]></schema>
<definition><![CDATA[<api-client name="people" schema-sha256="49a439854084d1c5f2beef5eb00249a6fd0dd54ce28a120963091f6340f264db"><operation id="getPerson"><parameter location="header" name="X-Mode" action="add" type="string" required="true"/><parameter location="query" name="status" action="add" type="array" item-type="integer" collection-format="multi"/></operation><authentication><scheme name="access" kind="bearer"/></authentication></api-client>]]></definition>
</api-client>
</api-clients>
<actor><actions><action name="person" label="Read person">
<input-schema><schema type="object" xmlns="urn:cococo:json-schema:1">
<property name="token" type="string"/><required name="token"/>
</schema></input-schema>
<source><![CDATA[
local input = ...
local client = ctx.api.get("people"):init(ctx.device.get("service"), {access={value=input.token}})
local ok, response = client:getPerson({path={id=1},headers={["X-Mode"]="fast"},query={status={1,2}}})
if not ok then error(response) end
return response.body.name
]]></source></action></actions></actor>
</integration>Header parameters use headers, for example {headers={["X-Mode"]="fast"}}.
Transport-controlled and authentication headers cannot be supplied as ordinary parameters.
Header control characters and parameters that conflict with declared auth are rejected.
Explicit default scalar styles are accepted. Scalar-element arrays support comma-delimited
headers and query values: Swagger collectionFormat="csv", or OpenAPI simple headers
and form query parameters with explode=false. Query arrays also support Swagger
ssv, tsv and pipes, and OpenAPI spaceDelimited and pipeDelimited with
explode=false. For example, {query={tags={"one","two"}}} produces a single query
value. Empty arrays produce an empty value. Empty elements or elements containing the
chosen separator fail before I/O because they cannot be represented unambiguously.
Repeated query keys are supported with Swagger collectionFormat="multi" and OpenAPI
form with explode=true (the default for form parameters). Array order and duplicate
values are preserved, and each value is encoded separately by the device. For example,
{query={status={"OrderEntered","AwaitingGoods"}}} becomes two status query values.
Empty arrays produce one empty value. Repeated values may contain commas or other
separators; these are encoded as part of the individual value.
Path arrays support Swagger csv and OpenAPI simple (either explode setting).
Elements are percent-encoded separately, with structural commas preserved between them.
Embedded commas become %2C, and empty elements are preserved. The same simple
path-array type is available through parameter amendments. OpenAPI 3 also supports
scalar/array label and matrix path styles declared in the uploaded schema. Label
arrays expand as .red,green or, with explode=true, .red.green; matrix arrays
expand as ;id=red,green or ;id=red;id=green. Empty lists have no expansion.
These follow RFC 6570. Reserved query expansion remains unsupported.
OpenAPI 3 flat object parameters retain their declared property types and required keys.
Path objects support simple, label and matrix; headers support simple; queries
support form, spaceDelimited, pipeDelimited and deepObject. For example,
{query={filter={name="Leia",age=19}}} with deepObject and explode=true emits
filter[age]=19 and filter[name]=Leia, encoded by the existing device layer.
With form and explode=true, it emits the property names directly. Expansion cannot
replace other parameters or authentication keys. Space/pipe forms require explode=false.
Declared nested or nullable members are unsupported; unconstrained extra values must be
non-null scalars at runtime. Empty or bracketed deep-object member names and ambiguous delimiters
in compact query/header values fail before I/O. Empty query/path objects have no expansion.
Swagger 2 does not support object parameters. Objects/styles are read from uploaded
schemas; the parameter amendment vocabulary still authors scalar and array shapes.
Serialization settings are part of integration resource binding compatibility.
Unsupported schema and serialization features produce compilation errors. Standalone
JSON Schema and SOAP are not API-client inputs.
Numeric format annotations retain the underlying Lua numeric types: int32 and int64
use integer; float and double use number. Integers remain integers through JSON
encoding and decoding, including values beyond double-precision's exact integer range.
Formats alone do not add width or range checks. Explicit minimum, maximum,
exclusiveMinimum and exclusiveMaximum constrain requests and responses, including
path, query, header and array-item parameters. Swagger 2 and OpenAPI 3.0 use boolean
exclusive flags alongside their bounds; OpenAPI 3.1 uses numeric exclusive bounds.
minLength and maxLength count Unicode code points, independently of UTF-8 byte size.
These value constraints retain ordinary Lua numeric/string types and are checked at
runtime before device I/O and when decoding responses. They also participate in binding
compatibility. multipleOf accepts a strictly positive numeric step and validates exact
decimal divisibility, including negative values and zero. It does not use a floating-point
tolerance: values already rounded by Lua arithmetic are checked as the resulting numeric
value. Patterns remain unsupported.
Array minItems, maxItems and uniqueItems, plus object minProperties and
maxProperties, are enforced on requests and responses. Counts include additional
properties and properties whose value is JSON null. Uniqueness compares JSON values:
object key order does not matter, array order does, and booleans are distinct from
numbers. These refinements retain the ordinary Lua array/object types and affect
binding compatibility. Use matching explicit array/object types in the schema.
Empty Lua request tables become [] when the schema unambiguously requires an array,
including nested arrays. Open JSON values and ambiguous array/object unions stay objects;
no defaults or missing fields are inserted. Nullable arrays retain JSON null separately
from empty arrays. Uniqueness has bounded visit/depth and payload work in addition to
the existing request/response size limits.
All string-valued formats, including service-specific names, are annotations. They
never change the declared primitive type or validate syntax; for example, a string
with format: date-time stays a string without timestamp validation. Malformed
non-string format metadata fails compilation.
format: byte is a string in all three dialects. OpenAPI 3.1 also accepts string-valued
contentEncoding and contentMediaType annotations. These preserve the supplied string;
they do not automatically encode, decode, parse embedded content or assert base64 validity.
Scripts choose when to apply a codec. Declared types, enums and length constraints still
apply. The content annotations in older dialects, malformed annotation values and
contentSchema produce compilation errors.
String, integer and boolean enums produce literal union types and reject values outside
the declaration in requests and responses. Required fields and nullable values are checked
independently on the JSON data, including nested objects and arrays. Use json.null()
for an explicit null; Lua nil omits a field. Nullable values project as T | JSONNull,
while optional fields additionally allow nil. A required nullable field must be supplied,
for example {body={name=json.null()}}; omission is rejected statically and before I/O.
Responses preserve null fields and null array elements. json.isnull(value) checks their
identity and narrows nullable objects and arrays in guarded branches.
OpenAPI 3.1 null-only schemas (type: "null" or type: ["null"]) project as
JSONNull and accept only explicit nulls. Empty Lua tables currently infer as any,
so the runtime also checks null identity and rejects forged values before device I/O.
Use json.array({}) for an explicit empty JSON array. An ordinary {} also becomes an
array when its request schema unambiguously requires one; ambiguous/open JSON stays an object.
These rules also apply to the scripted provider's login() request and response.
API inputs reject cycles and are limited to 20,000 nested values per argument and depth below 32.
Objects with schema-valued additionalProperties and no named properties become typed
string-keyed dictionaries. Their values are validated for requests and responses, and empty maps
remain JSON objects. additionalProperties: false rejects unknown response fields.
Objects that also declare named properties preserve those fields and their required/optional
status. Other keys use the additional-value schema. A known field may have a different
type from the additional values; its declared schema takes precedence. Reads of additional
keys are optional because a key may be absent. Omitted/true/empty-schema additional values
use recursive JSON-only types; functions and device handles remain invalid.
Request and response types differ where the schema marks fields readOnly or
writeOnly (OpenAPI 3). Read-only fields are excluded from requests; write-only
fields are excluded from responses. Required flags apply only on the relevant side.
The runtime rejects excluded fields, including a response that leaks a write-only value.
For local schema references, Swagger 2 and OpenAPI 3.0 ignore adjacent access annotations
and use the target's annotations. OpenAPI 3.1 combines adjacent annotations with those
on the target; false cannot remove a target restriction. Conflicting read-only and
write-only restrictions fail compilation. Supported assertion siblings apply in OpenAPI 3.1;
older dialects reject them. See adjacent schema assertions below.
allOf supports compatible structural intersections. OpenAPI anyOf and oneOf
produce union types; the runtime additionally enforces exactly one matching branch for
oneOf. Composition requires 1–32 branches and supported adjacent assertions intersect
with those branches. Unsupported intersections and conflicting access annotations produce diagnostics.
Expanded type and validation trees are bounded to 20,000 nodes and a 1 MiB size estimate
per projection; excessively deep or expanding schemas are rejected.
Recursive local schema references use finite named types. Self-references, mutually recursive models and recursive array elements retain their field types and directional access rules, including in provider login scripts. References never fetch external schemas. Alias cycles without a concrete schema fail compilation. Runtime validation shares a 20,000-visit budget across branches and permits at most 128 schema steps; exhaustion fails validation rather than accepting a partially checked value. The JSON value depth and size limits still apply.
Lua obtains the factory with ctx.api.get("people") and initializes it with an
explicit ctx.device.get("service") handle. The installation must grant that device
through a declared resource. Schema base URLs do not create outbound routes. Credentials
are supplied at initialization from installation/runtime inputs; variable declarations
contain types, never credential values. Auth-provider login uses the same device.
Declared Basic, Bearer (OpenAPI) and header/query API-key security requirements infer
initialization inputs. A scheme called login using Basic authentication requires
init(device, {login={username=user, password=password}}). A key or Bearer scheme
called token requires init(device, {token={value=tokenValue}}). These variables come
from runtime inputs. No auth argument is needed for an API without security requirements.
Operation-level security: [] removes inherited authentication. Combined requirements
need each named credential. Schema alternatives require an explicit API policy; OAuth
flows and cookie keys remain unsupported. A scripted Basic provider can supply Basic
credentials; its login operation must itself be public.
Declare <authentication> in the API definition to replace schema authentication for
its selected operations. Each <scheme> has a credential-slot name and a kind:
basic, bearer, headerKey or queryKey. API-key policies additionally require key,
the header or query name. Schemes in the same policy are all required. No credential
values or endpoint settings are accepted. This also resolves ambiguous schema alternatives.
An <authentication> inside an <operation> takes precedence over the API policy.
An empty <authentication/> explicitly makes that operation public, including when a
scripted provider is configured. If all selected operations are public, initialization
requires only the device and never invokes the provider. Omit the element to inherit. For a scripted provider
with a global Basic policy, declare its login operation with <authentication/>.
GraphQL uses the equivalent authentication list on APIClientInput and
APIClientOperationInput: omitted inherits, [] explicitly declares no authentication.
Every item has name, kind and optional key. Policy changes alter binding compatibility
and provider-cache identity; credentials remain arguments to typed init(device, ...).
This example receives a token as an action input:
<integration id="com.example.authpolicy" version="1.0.0" sdk-version="2.0">
<resources><resource id="service" type="protocol/http"/></resources>
<api-clients>
<api-client name="people">
<schema><![CDATA[{"swagger":"2.0","produces":["application/json"],"paths":{"/people/{id}":{"get":{"operationId":"getPerson","parameters":[{"name":"id","in":"path","required":true,"type":"integer"}],"responses":{"200":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"}}}}}}}}}]]></schema>
<definition><![CDATA[<api-client name="people" schema-sha256="49a439854084d1c5f2beef5eb00249a6fd0dd54ce28a120963091f6340f264db"><operation id="getPerson"/><authentication><scheme name="access" kind="bearer"/></authentication></api-client>]]></definition>
</api-client>
</api-clients>
<actor><actions><action name="person" label="Read person">
<input-schema><schema type="object" xmlns="urn:cococo:json-schema:1">
<property name="token" type="string"/><required name="token"/>
</schema></input-schema>
<source><![CDATA[
local input = ...
local client = ctx.api.get("people"):init(ctx.device.get("service"), {access={value=input.token}})
local ok, response = client:getPerson({path={id=1}})
if not ok then error(response) end
return response.body.name
]]></source></action></actions></actor>
</integration>A client declaration matching an apiclient resource requirement instead describes
the contract expected from a user-selected tenant client. It is not a fallback grant:
the instance must bind that resource, and its operation/auth-variable signatures must
match, including wire constraints and operation authentication. Other client declarations are integration-owned. Normal exposes permitted tenant
clients, Integration exposes its grants and owned clients, and Pure exposes neither.
Tenant-authored CustomApp versions, Workspace handlers, MCP tools and Lua discovery,
IaC Lua validation and Normal workflow-output inference use the authorized tenant catalog.
Downloadable Normal IaC testing contracts carry the same declarations; their ETag changes
with the catalog and the caller’s read grants. Authors with different grants can receive
different ETags for the same reference; account for this when sharing IaC lockfiles.
Workspace publication rechecks the authorized catalog without embedding that catalog
in the saved artifact.
Generic Integration-role editor requests have no instance bindings and therefore expose no API clients; the integration authoring compiler supplies the declared contracts. API declarations packaged in an integration belong to its actor and Actions. They do not grant API access to embedded CustomApp server APIs or inline workflow scripts, which are separate Normal-profile assets. Portable embedded assets cannot assume a tenant API catalog exists; keep packaged API operations in the Integration Actions. Direct tenant API calls can be authored on tenant CustomApp versions with the authorized catalog.
A tenant may persist up to 256 API definitions, including failed drafts. Updates and deletions remain available at the limit. An old definition that no longer compiles is omitted from the runtime catalog so unrelated scripts remain usable. Delete dependent API definitions before deleting their uploaded external schema.
The existing sourceXML @iac_artifact(language: XML, validators: ["integration-source"])
annotation covers the entire pair. nativeProjection remains computed. IaC uses the
same native compiler as source validation, create, update and publication. The generated
integration_document model includes apiClients; schema is a string to preserve
pinned bytes, and definition is XML text. No extra IaC resource or YAML authoring
surface is introduced. Exact XML export includes the pair; revision preconditions and
published-version immutability apply to the whole document.
Limits are 256 clients per document, 1 MiB per schema, 256 KiB per API definition,
and the existing 2 MiB integration source/projection budgets. Invalid schemas, pins,
names, overrides or provider scripts reject the document before persistence. Diagnostics
identify the client and the schema or definition field.
This deliberately invalid example has a mismatched schema pin and is rejected before any draft is written:
<integration id="com.example.people" version="1.0.0" sdk-version="2.0">
<resources><resource id="service" type="protocol/http"/></resources>
<api-clients>
<api-client name="people">
<schema><![CDATA[{"swagger":"2.0","produces":["application/json"],"paths":{"/people/{id}":{"get":{"operationId":"getPerson","parameters":[{"name":"id","in":"path","required":true,"type":"integer"}],"responses":{"200":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"}}}}}}}}}]]></schema>
<definition><![CDATA[<api-client name="people" schema-sha256="0000000000000000000000000000000000000000000000000000000000000000"><operation id="getPerson"/></api-client>]]></definition>
</api-client>
</api-clients>
<actor><actions><action name="person" label="Read person"><source><![CDATA[
local client = ctx.api.get("people"):init(ctx.device.get("service"))
local ok, response = client:getPerson({path={id=1}})
if not ok then error(response) end
return response.body.name
]]></source></action></actions></actor>
</integration>OpenAPI 3.0 and 3.1 use the same device-bound Lua API. These examples describe the same JSON POST and typed 201 response:
<integration id="com.example.people.openapi30" version="1.0.0" sdk-version="2.0">
<resources><resource id="service" type="protocol/http"/></resources>
<api-clients>
<api-client name="people">
<schema><![CDATA[{"openapi":"3.0.3","info":{"title":"People","version":"1.0"},"components":{"schemas":{"Person":{"type":"object","required":["name"],"properties":{"name":{"type":"string"}}}}},"paths":{"/people":{"post":{"operationId":"createPerson","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Person"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Person"}}}}}}}}}]]></schema>
<definition><![CDATA[<api-client name="people" schema-sha256="16b3fb30e7117deddd35f9190e480ed61c333821265e417f65501d9600bf7862"><operation id="createPerson"/></api-client>]]></definition>
</api-client>
</api-clients>
<actor><actions><action name="person" label="Create person"><source><![CDATA[
local client = ctx.api.get("people"):init(ctx.device.get("service"))
local ok, response = client:createPerson({body={name="Leia"}})
if not ok then error(response) end
return response.body.name
]]></source></action></actions></actor>
</integration><integration id="com.example.people.openapi31" version="1.0.0" sdk-version="2.0">
<resources><resource id="service" type="protocol/http"/></resources>
<api-clients>
<api-client name="people">
<schema><![CDATA[{"openapi":"3.1.0","info":{"title":"People","version":"1.0"},"components":{"schemas":{"Person":{"type":"object","required":["name"],"properties":{"name":{"type":"string"}}}}},"paths":{"/people":{"post":{"operationId":"createPerson","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Person"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Person"}}}}}}}}}]]></schema>
<definition><![CDATA[<api-client name="people" schema-sha256="f51c7332a928f0107785563d17e07328f0207fd960c293cf770a3810a3d1b135"><operation id="createPerson"/></api-client>]]></definition>
</api-client>
</api-clients>
<actor><actions><action name="person" label="Create person"><source><![CDATA[
local client = ctx.api.get("people"):init(ctx.device.get("service"))
local ok, response = client:createPerson({body={name="Leia"}})
if not ok then error(response) end
return response.body.name
]]></source></action></actions></actor>
</integration>Operation id is the Lua method name. Omit selectors to use the same operation ID
from the uploaded schema. Use source-id to give a uniquely named source operation
a different Lua name. For duplicate or missing operation IDs, supply both path and
method to select one existing operation; optional source-id verifies its original
ID too. These fields select a route already in the pinned upload and cannot create or
rewrite a destination. Duplicate names, ambiguous selections, mismatched source IDs
and missing routes fail compilation.
GraphQL uses APIClientOperationInput.sourceId, path and method. Parameter and auth
overrides remain children of the selected operation and apply only to that Lua method.
A provider's login-operation uses this same declared name. This example disambiguates
two source operations while keeping their original paths:
<integration id="com.example.operation-selection" version="1.0.0" sdk-version="2.0">
<resources><resource id="service" type="protocol/http"/></resources>
<api-clients>
<api-client name="people">
<schema><![CDATA[{"paths":{"/people/{id}":{"get":{"operationId":"getPerson","parameters":[{"in":"path","name":"id","required":true,"type":"integer"}],"responses":{"200":{"schema":{"properties":{"name":{"type":"string"}},"required":["name"],"type":"object"}}}}},"/robots/{id}":{"get":{"operationId":"getPerson","parameters":[{"in":"path","name":"id","required":true,"type":"integer"}],"responses":{"200":{"schema":{"properties":{"name":{"type":"string"}},"required":["name"],"type":"object"}}}}}},"produces":["application/json"],"swagger":"2.0"}]]></schema>
<definition><![CDATA[<api-client name="people" schema-sha256="cd89cf4faa4c872892c60d85c5be393c083ada9dc8706146a5e6f5b3fc183e56"><operation id="getHuman" source-id="getPerson" path="/people/{id}" method="GET"><parameter location="header" name="X-Mode" action="add" type="string" required="true"/><parameter location="query" name="status" action="add" type="array" item-type="integer" collection-format="multi"/></operation><operation id="getRobot" source-id="getPerson" path="/robots/{id}" method="GET"/><authentication><scheme name="access" kind="bearer"/></authentication></api-client>]]></definition>
</api-client>
</api-clients>
<actor><actions><action name="person" label="Read person">
<input-schema><schema type="object" xmlns="urn:cococo:json-schema:1">
<property name="token" type="string"/><required name="token"/>
</schema></input-schema>
<source><![CDATA[
local input = ...
local client = ctx.api.get("people"):init(ctx.device.get("service"), {access={value=input.token}})
local ok, response = client:getHuman({path={id=1},headers={["X-Mode"]="fast"},query={status={1,2}}})
if not ok then error(response) end
return response.body.name
]]></source></action></actions></actor>
</integration>Local references
Uploaded schemas may use local JSON Pointer references, including array indices and
escaped or empty property names. Percent encoding is decoded once, then ~0 and ~1
represent literal tilde and slash. Array indices must be canonical nonnegative integers
within the array. Malformed pointers and missing targets fail compilation. Named anchors
and references to other files or URLs remain unsupported; compilation never fetches them.
Adjacent schema assertions
OpenAPI 3.1 schemas may place supported assertions beside $ref; both the target and
siblings are enforced. Swagger 2/OpenAPI 3.0 assertion siblings remain rejected. Supported
constraints may also accompany composition keywords. They intersect with every applicable
composition result, including the exact-one requirement of oneOf.
A constraint-only schema (for example maxLength: 40) refines matching JSON values without
asserting their type. This is useful beside a reference that already supplies a type.
The generated Lua type retains the target's structural projection; value constraints are
checked before device I/O and on responses. Typeless structural schemas and arbitrary
union intersections remain unsupported and fail compilation. This composition support
applies to body schemas and nested values; parameter serialization still rejects
composition and reference assertion siblings.
Body media types
Clients support JSON (application/json and application media types ending in +json)
and UTF-8 text/*. Text bodies require string schemas and use raw text on the wire;
JSON bodies retain their structured Lua types. Empty text is a valid body. OpenAPI
content maps currently require one supported media type with an explicit schema.
Swagger selects application/json when offered, otherwise requires one supported type.
Response Content-Type, when present, must agree with the selected schema media type. UTF-8 charset declarations are accepted; other charsets and conflicting headers fail. A missing header uses the single declared schema. Both body directions retain the 1 MiB limit and schema validation. Arbitrary binary and multiple-media selection remain unsupported. Form and multipart requests are described below.
URL-encoded form requests
Swagger formData and OpenAPI 3 URL-encoded request bodies use the same typed body table
as JSON clients. Scalars, primitive arrays and flat objects use the existing device HTTP
call. OpenAPI arrays default to repeated fields; Swagger uses collectionFormat. Explicit
form, space/pipe-delimited array and supported flat-object query styles are available.
Delimited arrays reject values containing their separator or empty members.
OpenAPI 3.0 object fields default to form expansion; OpenAPI 3.1 defaults them to JSON-valued
fields unless style/explode/allowReserved is explicit. Both support explicit JSON contentType
for nested or nullable form fields. Headers in form encoding metadata are ignored. UTF-8
values are escaped, with spaces encoded as +; empty strings and empty repeated arrays
emit name=. Form responses and non-JSON contentType overrides are unsupported.
Required fields and value constraints are checked before sending. Expanded object keys cannot collide with declared fields. The encoded body must fit within 1 MiB. Nullable style-serialized fields and allowReserved=true remain unsupported. Binary transport requires a byte-preserving device contract.
Text and JSON multipart requests
Multipart/form-data accepts typed text/number/boolean fields, JSON object parts and arrays of supported parts through the supplied device. Swagger uses formData and collectionFormat; OpenAPI 3 uses its body schema and optional part contentType. Multipart is not a file handle or a new HTTP capability. Binary/file parts remain unsupported by the current string body contract; base64 strings can still be supplied as ordinary JSON/text data when declared that way.
Part names are quoted safely and boundaries cannot be injected through payload text. All headers and delimiters count toward the 1 MiB body limit. Arbitrary per-part headers and filenames are unsupported, and no defaults are injected. OpenAPI 3.1 style/explode/allowReserved multipart overrides and multipart responses are explicitly rejected. Untyped parts require an explicit JSON contentType; additional undeclared parts need a concrete value schema. Empty repeated arrays fail before I/O because zero parts cannot preserve field presence. OpenAPI 3.1 contentEncoding/contentMediaType on a part requires an explicit supported contentType; format remains an annotation.
Published platform manual · revision 304ecb37ac10