Blog • App Intents

How an iOS 27 Agent Calls a Third-Party App: Validate the App Intent JSON Before the API

When the system calls a third-party app, the entrance is the AppIntent you declared. After the parameters reach perform() and before any API call, write them as JSON. If the type, enum, or a required key does not match, return a failure record and stop inside the app.

How one request reaches an API is in the JSON path of an App Action. Whether Siri becomes an agent is in Will Siri AI become an AI agent. This article is only the app side: which parameters you declare, what the schema stops, and which JSON you return on failure.

Hold this first: A call that reaches your app does not mean the parameters are ready. Next: the contract you declare, one schema, three failures, a shape that still must stop, then how the failure goes back.

Declare the parameter contract

The third-party app's entrance is an AppIntent. You give it a title and implement perform(). The system can discover the action because you declared it. The declaration does not prove that these parameters may call the API.

Mark each parameter with @Parameter. See Adding parameters to an App Intent. This example accepts three things: an item id, a quantity, and a fulfillment method. Names, explanatory text, and extra keys from a model are not in the contract.

  1. 1
    Fix the Intent name

    The intent field in the candidate JSON may only be PlaceOrderIntent. Any other name means this call found the wrong action.

  2. 2
    List the three parameters

    itemId is a string, quantity is an integer, and fulfillment may only be pickup or delivery. If any one is missing, the candidate is incomplete.

  3. 3
    Validate at the start of perform()

    If validation fails, return the failure record below. Only after it passes, the id resolves, and the quantity is at least 1 do you call the API.

Turn the same fields into a schema

The candidate and schema below are your contract, not Apple's wire format. JSON Schema in JSONNote checks types, enums, and required keys. It cannot express that quantity must be at least 1 or that the id must exist. Those checks stay in the app.

A candidate that can go to the next check. fulfillment must be pickup or delivery.
{
  "intent": "PlaceOrderIntent",
  "parameters": {
    "itemId": "sku_42",
    "quantity": 1,
    "fulfillment": "pickup"
  }
}
Paste the candidate in the first field and this schema in the second. Set the operation to Check with Schema.
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "required": ["intent", "parameters"],
  "properties": {
    "intent": { "enum": ["PlaceOrderIntent"] },
    "parameters": {
      "type": "object",
      "required": ["itemId", "quantity", "fulfillment"],
      "properties": {
        "itemId": { "type": "string" },
        "quantity": { "type": "integer" },
        "fulfillment": { "enum": ["pickup", "delivery"] }
      }
    }
  }
}

How parameter types line up is in App action fields and JSON Schema. This article does not repeat that table. It only covers what the app returns when validation fails.

Three candidates that fail

What changed in the candidateWhy it fails
fulfillment set to shipIt is not pickup or delivery.
quantity set to the string 1The contract requires an integer.
itemId removedA required key is missing, so the candidate is incomplete.

Change quantity to 0 and the type is still an integer, so this schema passes. Passing is not an order. The app must reject a quantity of 0.

A valid shape can still stop

The failure record when quantity is below 1. dialog is the sentence shown to the person.
{
  "intent": "PlaceOrderIntent",
  "status": "rejected",
  "reason": "invalid_parameter",
  "field": "quantity",
  "dialog": "Quantity must be at least 1."
}

A non-empty itemId also passes the schema. Next, resolve that id in your own data. If it does not resolve, write another rejected record with field set to itemId. Do not invent an id from a product name, and do not call the API.

How a failure goes back

When a required value is missing, ask again with requestValue on that parameter. Do not call the API with an empty value. When a value is illegal, stop perform() and use IntentDialog to write the sentence the person can read. The JSON above is the record you keep for that decision.

Casestatus in the recordAPI
fulfillment is missingneeds_valueAsk again, do not call the API
fulfillment is shiprejectedSay it may only be pickup or delivery
quantity is 0rejectedStop even though the shape is valid
itemId does not resolverejectedDo not replace the id with a product name

needs_value means you still have to ask. rejected means this candidate cannot run. Neither proceeds to the API. The person sees the dialog sentence. The log keeps the whole JSON so you can later see which field stopped the call.

How this differs from the other articles

The request-path articleThe exchange-envelope articleThis article
AnswersHow a phrase reaches an APIWhich JSON sits between the model value and the parametersHow the app declares, validates, and returns a failure
Do not look here forAnother walk from speech to the APIThe two objects content and parametersReplacing an entity id with a product name

Stop the call in JSONNote

  1. 1
    Pick the right operation

    Open JSON Schema. Set Operation to Check with Schema. Paste the candidate in the first field and the schema above in the second. intent may only be PlaceOrderIntent. fulfillment may only be pickup or delivery. quantity must be an integer.

  2. 2
    Reject three bad documents

    Set fulfillment to ship, set quantity to the string 1, or delete itemId. Each of those fails. Changing quantity to 0 passes. After it passes, still write rejected and do not call the API.

  3. 3
    Diff 1 against 0

    Open JSON Compare Diff. Paste parameters with quantity 1 in the first field, and the same object with quantity 0 in the second. The difference is that number. 0 still passes the schema, so the failure record stops the API, not this shape check.

Common questions

If the system already handed the app the parameters, do you still validate?

Yes. The system handed you a call, not an API request that was already checked. At the start of perform(), read this JSON. If it does not pass, do not continue.

Can you call the API once the schema passes?

Not yet. This schema only checks type, enum, and required keys. quantity 0 passes. A string itemId also passes. Call the API only when the quantity is at least 1 and the id resolves.

If a parameter is missing, do you return an error or ask again?

Ask again. Set status to needs_value and use requestValue to ask the person for that value. Do not call the API before you have a legal value. If the value arrives and is illegal, write rejected.

Is this the same article as the JSON path of one request?

No. That article follows one request from a phrase to an API. This one stays inside the app: the parameter contract, the schema, and the JSON returned on failure. The API runs only when neither rejected nor needs_value was written.

Is the failure record Apple's response format?

No. Apple uses requestValue and IntentDialog to hand the result to the system and the person. This JSON is your own log: status, reason, field, and dialog. Keep it so you can review in JSONNote why the API was not called.

Summary

When a third-party app is called, there is first a parameter contract, then a JSON document that is allowed to fail. The failure record stops the call before the API and keeps one dialog sentence a person can read.

The decision: declare the Intent parameters first; let the schema stop bad types, enums, and missing keys; return needs_value when a value is missing; return rejected when a value is illegal; neither calls the API; quantity below 1 or a missing id stops the call even when the shape passes.

Next: let the candidate parameters pass the schema, then decide whether this call continues or returns a failure.

Back to the blog