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.
- Intent parameters are the app's own contract
- The schema checks type, enum, and required keys
- A failure is recorded as needs_value or rejected
- Neither of those records proceeds to the API
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.
- 1Fix the Intent name
The intent field in the candidate JSON may only be PlaceOrderIntent. Any other name means this call found the wrong action.
- 2List 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.
- 3Validate 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.
{
"intent": "PlaceOrderIntent",
"parameters": {
"itemId": "sku_42",
"quantity": 1,
"fulfillment": "pickup"
}
}
{
"$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 candidate | Why it fails |
|---|---|
| fulfillment set to ship | It is not pickup or delivery. |
| quantity set to the string 1 | The contract requires an integer. |
| itemId removed | A 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
{
"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.
| Case | status in the record | API |
|---|---|---|
| fulfillment is missing | needs_value | Ask again, do not call the API |
| fulfillment is ship | rejected | Say it may only be pickup or delivery |
| quantity is 0 | rejected | Stop even though the shape is valid |
| itemId does not resolve | rejected | Do 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 article | The exchange-envelope article | This article | |
|---|---|---|---|
| Answers | How a phrase reaches an API | Which JSON sits between the model value and the parameters | How the app declares, validates, and returns a failure |
| Do not look here for | Another walk from speech to the API | The two objects content and parameters | Replacing an entity id with a product name |
Stop the call in JSONNote
- 1Pick 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.
- 2Reject 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.
- 3Diff 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.