integrations / integrations-xml-17Markdown ↓

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