[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M698Z9P44T4M0H2FN34STK64":3,"skill-same:01M698Z9P44T4M0H2FN34STK64":104},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":26,"publishedAt":47,"linkOnly":48,"markdownContent":49,"markdownBody":50,"frontMatter":51,"refName":55,"commitSha":56,"skillDirectory":57,"forksCount":58,"repositoryHtmlUrl":59,"repositoryHomepageUrl":6,"ownerHtmlUrl":60,"ownerBlogUrl":61,"ownerDescription":62,"updatedAt":63,"install":64,"resources":77},"01M698Z9P44T4M0H2FN34STK64","application-integration-authoring",null,"Builds, versions, publishes, tests, and executes Google Cloud Application Integration workflows through the REST API and Python discovery client, and promotes versions across environments. Use when listing integrations, downloading or uploading integration versions as JSON, configuring API triggers or config variables, publishing drafts, invoking integrations, or automating CI\u002FCD promotion. Do not use for initial region provisioning and IAM setup (use application-integration-basics) or diagnosing failed runs and logs (use application-integration-troubleshooting).","google\u002Fskills","google","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F1342004?v=4","01M698VYEFDM0VJBZDYYJ2T83S","Google","01M698WR0H76ENHE587T9EJT89",23908,"Apache-2.0","https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapplication-integration-authoring\u002FSKILL.md",[18,23],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000010","后端与接口","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000011","运维与部署",[27,28,29,30,31,32,33,34,35,36,37,38,39,9,40,41,42,43,44,45,46],"application integration authoring","integration","authoring","application","api","listing","when","drafts","environments","Variables","CI\u002FCD","as","across","iam","ci","logs","config","workflows","google cloud","python","2026-11-10 10:12:30",false,"---\nname: application-integration-authoring\nmetadata:\n  version: \"1.0.2\"\n  category: ApplicationDevelopment\ndescription: >-\n  Builds, versions, publishes, tests, and executes Google Cloud Application Integration\n  workflows through the REST API and Python discovery client, and promotes versions across\n  environments. Use when listing integrations, downloading or uploading integration versions\n  as JSON, configuring API triggers or config variables, publishing drafts, invoking\n  integrations, or automating CI\u002FCD promotion. Do not use for initial region provisioning\n  and IAM setup (use application-integration-basics) or diagnosing failed runs and logs (use application-integration-troubleshooting).\n---\n\n# Application Integration Authoring\n\nGoogle Cloud Application Integration workflows are authored, versioned, published, and executed programmatically through the REST API (`https:\u002F\u002Fintegrations.googleapis.com\u002Fv1\u002F...`) or the discovery-based Google API Python client (`googleapiclient.discovery.build(\"integrations\", \"v1\")`).\n\nThere is no `gcloud integrations` command group, no Google-managed MCP server for Application Integration, and no documented Terraform resource for integrations or versions. Use `gcloud` only for authentication, enabling APIs, and IAM bindings. Use Terraform only for region provisioning (`google_integrations_client`), authentication profiles (`google_integrations_auth_config`), and Integration Connectors (`google_integration_connectors_connection`).\n\nFor initial project setup, region provisioning, and IAM role definitions, use the `application-integration-basics` skill. For diagnosing failed or stuck runs, execution logs, and quota errors, use the `application-integration-troubleshooting` skill.\n\n## Attribution\n\n**MUST READ**: Before executing any `gcloud` command, REST API call, or Python\ndiscovery-client call, you MUST read\n[Usage Attribution](references\u002Fattribution.md). For commands executed directly\nin the terminal (never in generated scripts, Terraform, ADK agents, or user\ncode), prefix `gcloud` with\n`CLOUDSDK_METRICS_REQUEST_ATTRIBUTION=\"agent-skills\u002F1.0 (skill:application-integration-authoring)\"`\nand add `agent-skills\u002F1.0 (skill:application-integration-authoring)` to the\n`User-Agent` header of `curl` requests.\n\n## Safety and Confirmation Rules\n\n- **Read-only operations run freely**: `integrations.list`, `integrations:search`, `versions.list`, `versions.get`, `versions:download`, `authConfigs.list`, and `executions.list`.\n- **Confirm before any state change or execution**: Before creating, uploading, patching, publishing, unpublishing, testing, executing, scheduling, or deleting an integration or version, or modifying IAM or auth configs, show the exact command and payload to the user and get explicit confirmation.\n- **Host changes require approval.** Before installing packages (`pip install`) or changing global gcloud or credential state (`gcloud config set ...` such as `auth\u002Fimpersonate_service_account`, `gcloud auth application-default login` \u002F `set-quota-project`), show the exact command and wait for the user's approval.\n- **Never store secrets in config variables**: Config variables (`CONFIG_*`) are stored and exported in plain text. Always use authentication profiles (`projects.locations.authConfigs`) or the Secret Manager access task for credentials.\n\n## Prerequisites Checklist\n\n1. **Install Google Cloud SDK**: Follow [Install the gcloud CLI](https:\u002F\u002Fdocs.cloud.google.com\u002Fsdk\u002Fdocs\u002Finstall-sdk.md.txt) if `gcloud` is not installed.\n2. **Billing and project**: Ensure the Google Cloud project has an active billing account attached (required even when usage stays within the free tier), then set the target project (changes global gcloud state; get the user's approval first):\n   ```bash\n   gcloud config set project PROJECT_ID\n   ```\n3. **Enable required APIs**:\n   ```bash\n   gcloud services enable integrations.googleapis.com connectors.googleapis.com secretmanager.googleapis.com\n   ```\n4. **Provision the region**: Application Integration is regional: confirm the target region is supported on [Application Integration locations](https:\u002F\u002Fdocs.cloud.google.com\u002Fapplication-integration\u002Fdocs\u002Flocations.md.txt) and use `us-central1` as a well-supported default when the user has no preference. The region is the `LOCATION` in every REST path; no `gcloud` default-region setting applies to Application Integration. The target `LOCATION` must be provisioned via the Cloud console or `POST ...\u002Fclients:provision` before authoring integrations (see `application-integration-basics`).\n5. **Authenticate**:\n   - For `curl`: run `gcloud auth login` (changes host or credential state; get the user's approval first) and pass `-H \"Authorization: Bearer $(gcloud auth print-access-token)\"`.\n   - For the Python client (install with `pip install \"google-api-python-client>=2.100.0,\u003C3\" \"google-auth>=2.20.0,\u003C3\"`): run `gcloud auth application-default login` and `gcloud auth application-default set-quota-project PROJECT_ID` (or set `GOOGLE_APPLICATION_CREDENTIALS` for a service account) (changes host or credential state; get the user's approval first).\n   - For service account impersonation: run `gcloud config set auth\u002Fimpersonate_service_account SA_EMAIL` (requires `roles\u002Fiam.serviceAccountTokenCreator`) (changes host or credential state; get the user's approval first).\n6. **Verify IAM roles**:\n   - Create, upload, or edit drafts: `roles\u002Fintegrations.integrationEditor` (covers `versions:upload`, which needs `integrations.integrationVersions.create`, and `versions:publish`)\n   - Publish or unpublish: `roles\u002Fintegrations.integrationDeployer` or `roles\u002Fintegrations.integrationEditor` (`roles\u002Fintegrations.integrationDeployer` alone can publish an existing version but cannot upload via `versions:upload`), plus `roles\u002Fintegrations.serviceAgent` granted to the Application Integration service agent (`service-PROJECT_NUMBER@gcp-sa-integrations.iam.gserviceaccount.com`)\n   - Invoke integrations: `roles\u002Fintegrations.integrationInvoker`\n   - Act as the run-as service account: integrations created on or after October 15, 2026 with any trigger other than an API or Private trigger (for example Schedule, Cloud Scheduler, Cloud Pub\u002FSub, Salesforce, or Connector Event; Private triggers are exempt like API triggers, so a sub-integration whose only trigger is Private can be published without `runAsServiceAccount`) cannot be tested or published unless `runAsServiceAccount` is set (and region governance requires it for all integrations); whenever `runAsServiceAccount` is set, the user publishing or testing the version needs `roles\u002Fiam.serviceAccountUser` on it (and at runtime, the invoking caller or trigger service account needs `roles\u002Fiam.serviceAccountUser` on it).\n\n## Decision Flow\n\n| Goal | Action | Endpoint \u002F Method |\n| :--- | :--- | :--- |\n| Inspect existing integrations in a region | List integrations, then list versions | `GET ...\u002Fintegrations` then `GET ...\u002Fversions` |\n| Export an integration version to source control | Download version JSON | `GET ...\u002Fversions\u002FVERSION_ID:download?fileFormat=JSON` |\n| Create an integration or add a new `DRAFT` version from JSON | Upload version JSON (creates the integration if it does not exist, or adds a new `DRAFT` version; still needs `:publish`) | `POST ...\u002Fintegrations\u002FINTEGRATION_NAME\u002Fversions:upload` |\n| Create a brand-new integration and fail on a name clash | Create first `DRAFT` version with `newIntegration=true` (returns `ALREADY_EXISTS` if `INTEGRATION_NAME` already exists) | `POST ...\u002Fintegrations\u002FINTEGRATION_NAME\u002Fversions?newIntegration=true` |\n| Edit an existing `DRAFT` version | Check `lockHolder`, then patch or upload | `PATCH ...\u002Fversions\u002FVERSION_ID?updateMask=...` |\n| Edit an `ACTIVE` (published) integration | Upload or create a new `DRAFT` version | `POST ...\u002Fintegrations\u002FINTEGRATION_NAME\u002Fversions:upload` |\n| Externalize environment-specific URLs or IDs | Define `CONFIG_\u003CNAME>` and supply at publish | `integrationConfigParameters` + `versions:publish` |\n| Store API keys, OAuth credentials, or certs | Create an authentication profile | `POST ...\u002FauthConfigs` |\n| Test an integration before production | Publish to a non-production project and invoke `:execute` | `POST ...\u002Fversions\u002FVERSION_ID:publish` then `POST ...\u002Fintegrations\u002FINTEGRATION_NAME:execute` |\n| Deploy a `DRAFT` to serve live traffic | Publish version with `configParameters` | `POST ...\u002Fversions\u002FVERSION_ID:publish` |\n| Invoke a published integration synchronously | Call `:execute` with `api_trigger\u002FTRIGGER_NAME` | `POST ...\u002Fintegrations\u002FINTEGRATION_NAME:execute` |\n| Invoke a published integration asynchronously | Call `:schedule` with `scheduleTime` | `POST ...\u002Fintegrations\u002FINTEGRATION_NAME:schedule` |\n| Promote a version across projects (CI\u002FCD) | Download JSON -> Upload to target -> Publish | `:download` -> `:upload` -> `:publish` |\n\nSee [references\u002Frest-api-cheatsheet.md](references\u002Frest-api-cheatsheet.md) for full request parameters, query filters, and IAM permissions for each method.\n\n## Core Authoring Workflow\n\n### 1. List Integrations and Download a Version\n\nList integrations in `LOCATION` and list versions for a specific `INTEGRATION_NAME`:\n\n```bash\nTOKEN=\"$(gcloud auth print-access-token)\"\n\n# List integrations in the region\ncurl -sS -H \"Authorization: Bearer ${TOKEN}\" \\\n  \"https:\u002F\u002Fintegrations.googleapis.com\u002Fv1\u002Fprojects\u002FPROJECT_ID\u002Flocations\u002FLOCATION\u002Fintegrations?pageSize=50\"\n\n# List versions (ordered by most recently modified)\ncurl -sS -H \"Authorization: Bearer ${TOKEN}\" \\\n  \"https:\u002F\u002Fintegrations.googleapis.com\u002Fv1\u002Fprojects\u002FPROJECT_ID\u002Flocations\u002FLOCATION\u002Fintegrations\u002FINTEGRATION_NAME\u002Fversions?orderBy=last_modified_time%20desc\"\n```\n\nDownload a specific version (`VERSION_ID` is the UUID at the end of the version's `name` field) as portable JSON:\n\n```bash\ncurl -sS -H \"Authorization: Bearer ${TOKEN}\" \\\n  \"https:\u002F\u002Fintegrations.googleapis.com\u002Fv1\u002Fprojects\u002FPROJECT_ID\u002Flocations\u002FLOCATION\u002Fintegrations\u002FINTEGRATION_NAME\u002Fversions\u002FVERSION_ID:download?fileFormat=JSON\" \\\n  | jq -r '.content' > integration-version.json\n```\n\n### 2. Understand the `IntegrationVersion` Structure\n\nAn `IntegrationVersion` JSON object contains four primary arrays and version-level settings:\n\n- `triggerConfigs`: Entry points that start the workflow. Each entry requires a unique `triggerNumber`, plus `triggerType` (such as `API` or `PRIVATE_TRIGGER`), optional `triggerId` (auto-generated from `properties` if omitted; for API triggers: `api_trigger\u002FTRIGGER_NAME`), and `startTasks` pointing to the first `taskId`.\n- `taskConfigs`: Units of work. Each entry needs `task` (the task type, such as `EmailTask` in the public upload sample, or `FieldMappingTask` in a discovery enum description), a unique `taskId`, `parameters`, `nextTasks`, and optional `failurePolicy` or `errorCatcherId`.\n- `integrationParameters`: Input, output, and transient variables (`key`, `dataType` such as `STRING_VALUE` or `JSON_VALUE`, `inputOutputType` of `IN`, `OUT`, `IN_OUT`, or `IN_OUT_TYPE_UNSPECIFIED`, and `defaultValue`).\n- `integrationConfigParameters`: Environment-specific config variable definitions (`parameter` with key `` `CONFIG_\u003CNAME>` `` and `parameter.defaultValue`; a top-level `value` in uploaded or created draft JSON is discarded, so per-environment values go in `configParameters` at publish).\n- `runAsServiceAccount`: Dedicated user-managed service account email. When set and no authentication profile (`authConfig`) is configured on the task, it is used to generate the auth token for Connector (`Connectors`), REST (`Call REST Endpoint`), and Cloud Function tasks; an authentication profile configured on the task takes precedence over `runAsServiceAccount`.\n\nAlways use `triggerConfigs`, `taskConfigs`, and `integrationParameters`. Never use `triggerConfigsInternal`, `taskConfigsInternal`, or `integrationParametersInternal` because they are deprecated. See [references\u002Fintegration-version-json.md](references\u002Fintegration-version-json.md) for field schemas, `ValueType` shapes, and a minimal `IntegrationVersion` JSON template.\n\n### 3. Create or Upload a `DRAFT` Version\n\nEach integration allows up to 100 versions and 100 tasks, and `INTEGRATION_NAME` can be at most 64 characters. Calling `versions:upload` creates a new `DRAFT` version—creating the integration automatically if `INTEGRATION_NAME` does not exist yet, or adding the next `DRAFT` version if it already exists (for example, uploading onto version 5 creates version 6). The uploaded version is in `DRAFT` state and still requires `versions:publish` to serve traffic. Use `POST ...\u002Fversions?newIntegration=true` (passing an `IntegrationVersion` JSON body) only when you explicitly want the call to fail with `ALREADY_EXISTS` if `INTEGRATION_NAME` already exists.\n\n```bash\n# Upload integration-version.json as a new DRAFT version\nPAYLOAD=\"$(jq -n --rawfile content integration-version.json '{content: $content, fileFormat: \"JSON\"}')\"\n\ncurl -sS --fail-with-body -X POST \\\n  -H \"Authorization: Bearer $(gcloud auth print-access-token)\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d \"${PAYLOAD}\" \\\n  \"https:\u002F\u002Fintegrations.googleapis.com\u002Fv1\u002Fprojects\u002FPROJECT_ID\u002Flocations\u002FLOCATION\u002Fintegrations\u002FINTEGRATION_NAME\u002Fversions:upload\"\n```\n\nCapture the new `VERSION_ID` from `.integrationVersion.name` in the response (`projects\u002FPROJECT_ID\u002Flocations\u002FLOCATION\u002Fintegrations\u002FINTEGRATION_NAME\u002Fversions\u002FVERSION_ID`).\n\n### 4. Version States and Edit-Lock Rules\n\n| State | Meaning | Editable? | Can Serve Triggers? |\n| :--- | :--- | :--- | :--- |\n| `DRAFT` | Version actively being edited; its edit lock is held by its author (`lockHolder` email) for 15 minutes from the last update | Yes (only by `lockHolder`) | No |\n| `ACTIVE` | Published version serving live traffic | No | Yes |\n| `SNAPSHOT` | Prior draft (when another author takes over in the console) or unpublished version (`PREVIOUSLY_PUBLISHED`) | No | No |\n| `ARCHIVED` | Soft-deleted via `versions.delete` (no undelete; listed as `DELETED` on the Integration versions documentation page) | No | No |\n\n- **Edit-lock enforcement**: A `DRAFT` version's edit lock is held by its author (`lockHolder`). Calling `versions:publish` or `versions.delete` on a `DRAFT` fails if the caller is not the `lockHolder` (`versions:publish` and `versions.delete` release any existing lock when they succeed).\n- **No v1 lock-takeover API**: `takeoverEditLock` does **not** exist on `projects.locations.integrations.versions` in the v1 REST API (it only exists on `versions.testCases`). If a `DRAFT` is locked by another user, wait 15 minutes from `updateTime` for the lock to expire automatically, or click **Take Over** in the Cloud console (which converts the existing draft to `SNAPSHOT` and creates a new `DRAFT`).\n\n### 5. Config Variables vs Authentication Profiles\n\n- **Config variables (`CONFIG_\u003CNAME>`)**: Use for non-secret environment values (backend hostnames, Pub\u002FSub topics, connection names). Define them in `integrationConfigParameters` on the draft (putting any default in `parameter.defaultValue`, because a top-level `value` on a draft is discarded) and pass their per-environment values in `configParameters` when calling `versions:publish`. **Gotcha**: Keys in `configParameters` at publish must **exactly match** a key in `integrationConfigParameters`, backticks included (for example, `` \"`CONFIG_target_url`\" ``); unknown keys are **silently ignored**, not rejected (`versions:publish` returns 200 and the default stays). Config variables cannot configure Integration Connectors entity operations or actions.\n- **Authentication profiles (`authConfigs`)**: Use for API keys, OAuth 2.0 credentials, JWTs, OIDC tokens, SSL\u002FTLS certificates, and service accounts. Auth profiles are regional resources (`projects\u002FPROJECT_ID\u002Flocations\u002FLOCATION\u002FauthConfigs\u002FAUTH_CONFIG_ID`) and are **not** included when you download an integration version. Create them in each target project via `POST ...\u002FauthConfigs` or Terraform `google_integrations_auth_config`.\n\n### 6. Test, Publish, and Unpublish\n\n- **Recommended testing path**: Test integrations by publishing the version in a non-production project (`versions:publish`) and invoking `integrations:execute` there.\n  - **Note on `integrations:test`**: The v1 REST API includes `POST ...\u002Fintegrations\u002FINTEGRATION_NAME:test` for testing a `DRAFT`, which requires `TestIntegrationsRequest.clientId` (\"Required. This is used to identify the client on whose behalf the event will be executed.\"), but the public documentation does not specify what value to pass. Do not invent a `clientId` value; publish to a non-production project and call `:execute` instead. (Draft testing also cannot run `Approval`, `Suspend`, or `Timer` tasks, and Call REST Endpoint authentication profile changes only take effect after publishing.)\n- **Publish (`versions:publish`)**: Deploys the version as `ACTIVE` and releases any edit lock (fails if the version is already `ACTIVE` or `ARCHIVED`, or if it is a `DRAFT` whose `lockHolder` differs from the caller). Any previously `ACTIVE` version is automatically unpublished, and dependent services switch to the newly published version.\n  - **Gotcha**: Publishing a new version automatically cancels all suspended executions of the previous version.\n  - **Gotcha**: Due to eventual consistency, the previous version can continue running for up to 10 minutes after a new version is published.\n- **Unpublish (`versions:unpublish`)**: Moves the `ACTIVE` version to `SNAPSHOT` (tagged `PREVIOUSLY_PUBLISHED`; fails if the version is not `ACTIVE`) and stops triggers and dependent services.\n\n```bash\n# Publish VERSION_ID and supply config variable values (keys must exactly match integrationConfigParameters keys, backticks included; unknown keys are silently ignored)\ncurl -sS --fail-with-body -X POST \\\n  -H \"Authorization: Bearer $(gcloud auth print-access-token)\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"configParameters\": {\"`CONFIG_backend_url`\": \"https:\u002F\u002Fapi.example.com\"}}' \\\n  \"https:\u002F\u002Fintegrations.googleapis.com\u002Fv1\u002Fprojects\u002FPROJECT_ID\u002Flocations\u002FLOCATION\u002Fintegrations\u002FINTEGRATION_NAME\u002Fversions\u002FVERSION_ID:publish\"\n```\n\n### 7. Execute (Sync) vs Schedule (Async)\n\nOnly an `ACTIVE` (published) version can be invoked via `execute` or `schedule`. For API triggers, `triggerId` is always `api_trigger\u002FTRIGGER_NAME` (where `TRIGGER_NAME` is at most 64 characters). Passing `-` as `INTEGRATION_NAME` in `execute` runs all published integrations in that region that share the `triggerId`.\n\n| Method | Mode | Max Timeout | Max API Input | Key Request Fields | Key Response Fields |\n| :--- | :--- | :--- | :--- | :--- | :--- |\n| `integrations:execute` | Sync | 2 minutes | 8 MB | `triggerId`, `inputParameters`, `requestId`, `doNotPropagateError` | `executionId`, `outputParameters`, `parameters` |\n| `integrations:schedule` | Async | 10 minutes | Not documented | `triggerId`, `scheduleTime` (RFC 3339), `inputParameters`, `requestId`, `userGeneratedExecutionId` | `executionInfoIds[]` |\n\n- **Reading `outputParameters` and `executionId`**: In `ExecuteIntegrationsResponse`, `executionId` identifies the run, and `outputParameters` is a JSON object of output variables (system-generated keys such as `` `ExecutionId` `` are wrapped in backticks).\n- **HTTP 200 does not prove every task succeeded**: If `doNotPropagateError` is `true`, or a task uses failure policy `IGNORE` or `NONE` with an alternate path, the HTTP call returns `200 OK` even when a task failed. Always verify the execution record via `executions.list?filter=execution_id=\"EXECUTION_ID\"` (checking `executionDetails.state`, each task's `taskExecutionState`, and `ErrorInfo` in `Execution.responseParameters`; when `doNotPropagateError` is `true`, errors are also returned in event parameters as `PostWithTriggerIdExecutionException`). Do not rely on the legacy\u002Fdeprecated `executionFailed` field.\n\nSee [references\u002Ftriggers-and-execution.md](references\u002Ftriggers-and-execution.md) for all trigger types, `ValueType` input formatting, OpenAPI spec generation, and suspension lifting and approval resolution.\n\n## End-to-End Version and API Workflow Examples\n\nIn downloaded and uploaded `IntegrationVersion` JSON, an `EmailTask` uses four customer-visible parameter keys inside `taskConfigs[].parameters`: `\"To\"` (`stringArray` of recipient addresses), `\"Subject\"` (`stringValue`), `\"BodyFormat\"` (`stringValue`, such as `\"PLAIN_TEXT\"`), and `\"TextBody\"` (`stringValue`). Both runnable examples below also set `\"runAsServiceAccount\": \"RUN_AS_SA\"` (replace `RUN_AS_SA` with your dedicated service account email, such as `order-workflow-sa@PROJECT_ID.iam.gserviceaccount.com`; projects with governance require a run-as service account to publish, and the caller needs `roles\u002Fiam.serviceAccountUser` on it). Executing an `EmailTask` sends a real email, so always confirm the recipient address (`RECIPIENT_EMAIL`) and each state-changing command (upload, publish, and execute) with the user before running.\n\n### Example 1: Upload, Publish, and Invoke Workflow with `curl`\n\nThis example uploads a runnable `IntegrationVersion` JSON (`versions:upload` creates `INTEGRATION_NAME` automatically if it does not exist yet, or adds a new `DRAFT` version if it does), publishes the draft, and invokes `:execute`. The caller must hold `roles\u002Fiam.serviceAccountUser` on `RUN_AS_SA` (projects with governance require a run-as service account to publish). Because `EmailTask` sends real email, confirm `RECIPIENT_EMAIL` and get user confirmation before running the upload, publish, and execute calls.\n\n```bash\nset -euo pipefail\nexport PROJECT_ID=\"my-project\"\nexport LOCATION=\"us-central1\"\nexport INTEGRATION_NAME=\"order-webhook\"\nexport TRIGGER_NAME=\"order_api\"\nTOKEN=\"$(gcloud auth print-access-token)\"\nBASE_URL=\"https:\u002F\u002Fintegrations.googleapis.com\u002Fv1\u002Fprojects\u002F${PROJECT_ID}\u002Flocations\u002F${LOCATION}\"\n\n# 1. Write an IntegrationVersion JSON file (set RUN_AS_SA and confirm RECIPIENT_EMAIL with user first; caller needs roles\u002Fiam.serviceAccountUser on RUN_AS_SA)\ncat \u003C\u003C'EOF' > \u002Ftmp\u002Fversion.json\n{\n  \"description\": \"API-triggered order notification integration\",\n  \"runAsServiceAccount\": \"RUN_AS_SA\",\n  \"triggerConfigs\": [\n    {\n      \"label\": \"API Trigger\",\n      \"triggerType\": \"API\",\n      \"triggerNumber\": \"1\",\n      \"triggerId\": \"api_trigger\u002Forder_api\",\n      \"properties\": {\"Trigger name\": \"order_api\"},\n      \"startTasks\": [{\"taskId\": \"1\"}]\n    }\n  ],\n  \"taskConfigs\": [\n    {\n      \"task\": \"EmailTask\",\n      \"taskId\": \"1\",\n      \"displayName\": \"Send Email\",\n      \"taskExecutionStrategy\": \"WHEN_ALL_SUCCEED\",\n      \"parameters\": {\n        \"To\": {\"key\": \"To\", \"value\": {\"stringArray\": {\"stringValues\": [\"RECIPIENT_EMAIL\"]}}},\n        \"Subject\": {\"key\": \"Subject\", \"value\": {\"stringValue\": \"Order notification for $orderId$\"}},\n        \"BodyFormat\": {\"key\": \"BodyFormat\", \"value\": {\"stringValue\": \"PLAIN_TEXT\"}},\n        \"TextBody\": {\"key\": \"TextBody\", \"value\": {\"stringValue\": \"Processing order $orderId$.\"}}\n      }\n    }\n  ],\n  \"integrationParameters\": [\n    {\n      \"key\": \"orderId\",\n      \"dataType\": \"STRING_VALUE\",\n      \"inputOutputType\": \"IN_OUT\",\n      \"defaultValue\": {\"stringValue\": \"ORD-0000\"}\n    }\n  ]\n}\nEOF\n\n# 2. Upload as a new DRAFT version — creates INTEGRATION_NAME if it does not exist yet (confirm with user first)\nUPLOAD_BODY=\"$(jq -n --rawfile content \u002Ftmp\u002Fversion.json '{content: $content, fileFormat: \"JSON\"}')\"\nVERSION_NAME=\"$(curl -sS --fail-with-body -X POST \\\n  -H \"Authorization: Bearer ${TOKEN}\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d \"${UPLOAD_BODY}\" \\\n  \"${BASE_URL}\u002Fintegrations\u002F${INTEGRATION_NAME}\u002Fversions:upload\" \\\n  | jq -r '.integrationVersion.name')\"\nVERSION_ID=\"${VERSION_NAME##*\u002F}\"\n[[ -n \"${VERSION_ID}\" && \"${VERSION_ID}\" != \"null\" ]] || { echo \"upload failed: ${VERSION_NAME}\" >&2; exit 1; }\n\n# 3. Publish the DRAFT version (confirm with user first)\ncurl -sS --fail-with-body -X POST \\\n  -H \"Authorization: Bearer ${TOKEN}\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{}' \\\n  \"${BASE_URL}\u002Fintegrations\u002F${INTEGRATION_NAME}\u002Fversions\u002F${VERSION_ID}:publish\"\n\n# 4. Invoke the published version synchronously — sends a real email to RECIPIENT_EMAIL (confirm with user first)\ncurl -sS --fail-with-body -X POST \\\n  -H \"Authorization: Bearer ${TOKEN}\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -H \"X-Server-Timeout: 120\" \\\n  -d \"{\n    \\\"triggerId\\\": \\\"api_trigger\u002F${TRIGGER_NAME}\\\",\n    \\\"inputParameters\\\": {\n      \\\"orderId\\\": {\\\"stringValue\\\": \\\"ORD-1001\\\"}\n    }\n  }\" \\\n  \"${BASE_URL}\u002Fintegrations\u002F${INTEGRATION_NAME}:execute\" \\\n  | jq '{executionId, outputParameters}'\n```\n\n### Example 2: Python Discovery Client Workflow (Built from the API Reference)\n\nNo official Python sample is published on `docs.cloud.google.com`; the following workflow is built from the v1 API discovery document (`google-api-python-client`). The caller must hold `roles\u002Fiam.serviceAccountUser` on `RUN_AS_SA` (projects with governance require a run-as service account to publish). Because `EmailTask` sends real email, confirm `RECIPIENT_EMAIL` and each state-changing call (`upload`, `publish`, `execute`) with the user before running. Install dependencies first with `pip install \"google-api-python-client>=2.100.0,\u003C3\" \"google-auth>=2.20.0,\u003C3\"` (changes host or credential state; get the user's approval first).\n\n```python\nimport json\nfrom googleapiclient.discovery import build\n\nPROJECT_ID = \"PROJECT_ID\"\nLOCATION = \"LOCATION\"\nINTEGRATION_NAME = \"INTEGRATION_NAME\"\nTRIGGER_NAME = \"TRIGGER_NAME\"\nRECIPIENT_EMAIL = \"RECIPIENT_EMAIL\"\nparent = f\"projects\u002F{PROJECT_ID}\u002Flocations\u002F{LOCATION}\"\nintegration_path = f\"{parent}\u002Fintegrations\u002F{INTEGRATION_NAME}\"\n\nservice = build(\"integrations\", \"v1\")\nintegrations_api = service.projects().locations().integrations()\nversions_api = integrations_api.versions()\n\n# 1. List existing integrations (read-only)\nlisted = integrations_api.list(parent=parent, pageSize=20).execute()\nfor item in listed.get(\"integrations\", []):\n    print(item[\"name\"], \"active=\", item.get(\"active\"))\n\n# 2. Upload a DRAFT version — creates INTEGRATION_NAME if it does not exist yet (set RUN_AS_SA and confirm RECIPIENT_EMAIL and action with user before running; caller needs roles\u002Fiam.serviceAccountUser on RUN_AS_SA)\nversion_dict = {\n    \"description\": \"API-triggered order notification integration\",\n    \"runAsServiceAccount\": \"RUN_AS_SA\",\n    \"triggerConfigs\": [{\n        \"label\": \"API Trigger\",\n        \"triggerType\": \"API\",\n        \"triggerNumber\": \"1\",\n        \"triggerId\": f\"api_trigger\u002F{TRIGGER_NAME}\",\n        \"properties\": {\"Trigger name\": TRIGGER_NAME},\n        \"startTasks\": [{\"taskId\": \"1\"}],\n    }],\n    \"taskConfigs\": [{\n        \"task\": \"EmailTask\",\n        \"taskId\": \"1\",\n        \"displayName\": \"Send Email\",\n        \"taskExecutionStrategy\": \"WHEN_ALL_SUCCEED\",\n        \"parameters\": {\n            \"To\": {\"key\": \"To\", \"value\": {\"stringArray\": {\"stringValues\": [RECIPIENT_EMAIL]}}},\n            \"Subject\": {\"key\": \"Subject\", \"value\": {\"stringValue\": \"Order notification for $orderId$\"}},\n            \"BodyFormat\": {\"key\": \"BodyFormat\", \"value\": {\"stringValue\": \"PLAIN_TEXT\"}},\n            \"TextBody\": {\"key\": \"TextBody\", \"value\": {\"stringValue\": \"Processing order $orderId$.\"}},\n        },\n    }],\n    \"integrationParameters\": [{\n        \"key\": \"orderId\",\n        \"dataType\": \"STRING_VALUE\",\n        \"inputOutputType\": \"IN_OUT\",\n        \"defaultValue\": {\"stringValue\": \"ORD-0000\"},\n    }],\n}\nupload_resp = versions_api.upload(\n    parent=integration_path,\n    body={\"content\": json.dumps(version_dict), \"fileFormat\": \"JSON\"},\n).execute()\nversion_name = upload_resp[\"integrationVersion\"][\"name\"]\n\n# 3. Publish the DRAFT version (confirm with user before running)\nversions_api.publish(\n    name=version_name,\n    body={\"configParameters\": {}},\n).execute()\n\n# 4. Invoke the published version synchronously — sends a real email to RECIPIENT_EMAIL (confirm with user before running)\nexec_resp = integrations_api.execute(\n    name=integration_path,\n    body={\n        \"triggerId\": f\"api_trigger\u002F{TRIGGER_NAME}\",\n        \"inputParameters\": {\"orderId\": {\"stringValue\": \"ORD-1001\"}},\n    },\n).execute()\nprint(\"executionId:\", exec_resp.get(\"executionId\"))\nprint(\"outputParameters:\", exec_resp.get(\"outputParameters\"))\n```\n\n## Common Task Gotchas and Limits\n\n- **Data limits**: Total cumulative integration data (including inputs and outputs) is capped at 30 MB. Above 10 MB, local execution logging is automatically disabled.\n- **Data Mapping**: Input and output types must match (cast with `TO_STRING`, `TO_INT`, and so on). Always set a fallback value or null inputs fail the task.\n- **Call REST Endpoint**: Reaches the public internet only (cannot reach VPC-internal hosts). Fails on HTTP 4xx and 5xx responses. Request max 20 MB, response max 34 MB, max 100 concurrent calls.\n- **JavaScript**: Uses the V8 engine (Rhino was deprecated January 24, 2025). Never remove the `executesScript(event)` entrypoint. Max script request size is 25 MB.\n- **Connectors task**: Requires an active Integration Connectors connection (billed separately). Timeout is 3 minutes; Application Integration quotas cap data to and from connections at 8 MB (Integration Connectors quotas list a 10 MB Connectors task payload limit; response max 20 MB).\n- **Approval vs Suspend**: Approval tasks can be approved or rejected via `POST ...\u002Fexecutions\u002FEXECUTION_ID\u002Fsuspensions\u002FSUSPENSION_ID:resolve` with body `{\"suspension\": {\"eventExecutionInfoId\": \"EXECUTION_ID\", \"integration\": \"INTEGRATION_NAME\", \"taskId\": \"TASK_ID\", \"state\": \"LIFTED\"}}` (or `\"REJECTED\"`), which is the same call the Cloud console Approve\u002FReject buttons make and requires `integrations.suspensions.resolve`. Suspend tasks are resumed via `POST ...\u002Fintegrations\u002FINTEGRATION_NAME\u002Fexecutions\u002FEXECUTION_ID\u002Fsuspensions\u002FSUSPENSION_ID:lift` (where `-` can replace `INTEGRATION_NAME` and either `EXECUTION_ID` or `SUSPENSION_ID`, provided at least one of `EXECUTION_ID` or `SUSPENSION_ID` is concrete; accepts an optional `suspensionResult` defaulting to `Resolved`), which requires `integrations.suspensions.lift`. Listing suspensions (`suspensions.list`) requires `integrations.suspensions.list`. All three permissions are included in `roles\u002Fintegrations.suspensionResolver` (and `roles\u002Fintegrations.integrationAdmin`); resuming also requires `roles\u002Fiam.serviceAccountUser` on the integration's run-as service account, requires user confirmation first, and runs the remainder of the execution in async mode.\n- **Loops and Sub-integrations**: For Each Loop and While Loop allow up to 8,000 iterations; For Each Parallel allows up to 10,000 iterations (default parallelism 50). For sub-integration calls (`Call Integration`, `For Each Loop`, `For Each Parallel`), child responses\u002Foutput parameters are returned to the parent only in `SYNC` mode (`ASYNC` calls return no output parameters); avoid `SYNC` mode if the sub-integration exceeds 2 minutes or includes an Approval task.\n\nSee [references\u002Ftasks-and-gotchas.md](references\u002Ftasks-and-gotchas.md) for the full task catalog, limits, variable reference syntax, and retry strategies.\n\n## Promoting Between Environments (CI\u002FCD)\n\nTo promote an integration from development to staging or production:\n\n1. Externalize environment-specific values (endpoints, project IDs, connection names) as `CONFIG_\u003CNAME>` config variables.\n2. Provision the target region, run-as service account, Integration Connectors connections, and authentication profiles (`authConfigs`) in the target project first (these infrastructure resources are not created by version uploads).\n3. Download the tested version JSON (`versions:download?fileFormat=JSON`), commit it to Git alongside per-environment config files, rewrite `.runAsServiceAccount` to the target environment's run-as service account (`jq --arg sa \"$RUN_AS_SA\" '.runAsServiceAccount = $sa' integration.json > integration.env.json`) before uploading, and run `versions:upload` + `versions:publish` from Cloud Build or your CI runner (`versions:upload` creates the integration automatically in the target project if it does not exist yet, producing a `DRAFT` that `:publish` activates).\n\nSee [references\u002Fcicd-promotion.md](references\u002Fcicd-promotion.md) for the complete promotion checklist and a ready-to-use Cloud Build pipeline.\n\n## Cleanup and Cost Management\n\n- **Pricing**: An active billing account must be attached to the project even when usage stays within the monthly free tier. Application Integration offers a monthly free tier for executions, data processed, and Google service connector nodes, after which executions, data processed, and Integration Connectors nodes incur charges. Review current thresholds and rates on the [Application Integration pricing page](https:\u002F\u002Fcloud.google.com\u002Fapplication-integration\u002Fpricing) and estimate costs with the Google Cloud Pricing Calculator.\n- **Unpublish to stop triggers**: Run `POST ...\u002Fversions\u002FVERSION_ID:unpublish` (after user confirmation) to stop scheduled, Pub\u002FSub, or API triggers from firing.\n- **Delete versions or integrations**: `DELETE ...\u002Fversions\u002FVERSION_ID` archives a single version (deleting all versions deletes the integration). `DELETE ...\u002Fintegrations\u002FINTEGRATION_NAME` permanently deletes the integration and all its versions.\n- **Connector nodes**: Unpublishing or deleting an integration does not delete Integration Connectors connections. Delete unused connections at `https:\u002F\u002Fconsole.cloud.google.com\u002Fconnectors\u002Fconnections` to stop hourly node charges.\n\n## Reference Guides\n\n- [Usage Attribution](references\u002Fattribution.md): Usage attribution for `gcloud` CLI commands, direct `curl` REST calls, and Python discovery-client snippets run in the terminal.\n- [REST API Cheatsheet](references\u002Frest-api-cheatsheet.md): v1 and v2 endpoints, parameters, filters, and IAM permissions.\n- [IntegrationVersion JSON Reference](references\u002Fintegration-version-json.md): Schema for `triggerConfigs`, `taskConfigs`, `integrationParameters`, `ValueType`, and config variables.\n- [Triggers and Execution Reference](references\u002Ftriggers-and-execution.md): Trigger types, `triggerId` formats, `:execute` vs `:schedule`, OpenAPI generation, and lifting or resolving suspensions.\n- [Tasks and Gotchas Reference](references\u002Ftasks-and-gotchas.md): Task-by-task rules, quotas, variable reference syntax, and error handling strategies.\n- [CI\u002FCD Promotion Reference](references\u002Fcicd-promotion.md): Cross-project promotion checklist, environment config pattern, and Cloud Build workflow.\n","# Application Integration Authoring\n\nGoogle Cloud Application Integration workflows are authored, versioned, published, and executed programmatically through the REST API (`https:\u002F\u002Fintegrations.googleapis.com\u002Fv1\u002F...`) or the discovery-based Google API Python client (`googleapiclient.discovery.build(\"integrations\", \"v1\")`).\n\nThere is no `gcloud integrations` command group, no Google-managed MCP server for Application Integration, and no documented Terraform resource for integrations or versions. Use `gcloud` only for authentication, enabling APIs, and IAM bindings. Use Terraform only for region provisioning (`google_integrations_client`), authentication profiles (`google_integrations_auth_config`), and Integration Connectors (`google_integration_connectors_connection`).\n\nFor initial project setup, region provisioning, and IAM role definitions, use the `application-integration-basics` skill. For diagnosing failed or stuck runs, execution logs, and quota errors, use the `application-integration-troubleshooting` skill.\n\n## Attribution\n\n**MUST READ**: Before executing any `gcloud` command, REST API call, or Python\ndiscovery-client call, you MUST read\n[Usage Attribution](references\u002Fattribution.md). For commands executed directly\nin the terminal (never in generated scripts, Terraform, ADK agents, or user\ncode), prefix `gcloud` with\n`CLOUDSDK_METRICS_REQUEST_ATTRIBUTION=\"agent-skills\u002F1.0 (skill:application-integration-authoring)\"`\nand add `agent-skills\u002F1.0 (skill:application-integration-authoring)` to the\n`User-Agent` header of `curl` requests.\n\n## Safety and Confirmation Rules\n\n- **Read-only operations run freely**: `integrations.list`, `integrations:search`, `versions.list`, `versions.get`, `versions:download`, `authConfigs.list`, and `executions.list`.\n- **Confirm before any state change or execution**: Before creating, uploading, patching, publishing, unpublishing, testing, executing, scheduling, or deleting an integration or version, or modifying IAM or auth configs, show the exact command and payload to the user and get explicit confirmation.\n- **Host changes require approval.** Before installing packages (`pip install`) or changing global gcloud or credential state (`gcloud config set ...` such as `auth\u002Fimpersonate_service_account`, `gcloud auth application-default login` \u002F `set-quota-project`), show the exact command and wait for the user's approval.\n- **Never store secrets in config variables**: Config variables (`CONFIG_*`) are stored and exported in plain text. Always use authentication profiles (`projects.locations.authConfigs`) or the Secret Manager access task for credentials.\n\n## Prerequisites Checklist\n\n1. **Install Google Cloud SDK**: Follow [Install the gcloud CLI](https:\u002F\u002Fdocs.cloud.google.com\u002Fsdk\u002Fdocs\u002Finstall-sdk.md.txt) if `gcloud` is not installed.\n2. **Billing and project**: Ensure the Google Cloud project has an active billing account attached (required even when usage stays within the free tier), then set the target project (changes global gcloud state; get the user's approval first):\n   ```bash\n   gcloud config set project PROJECT_ID\n   ```\n3. **Enable required APIs**:\n   ```bash\n   gcloud services enable integrations.googleapis.com connectors.googleapis.com secretmanager.googleapis.com\n   ```\n4. **Provision the region**: Application Integration is regional: confirm the target region is supported on [Application Integration locations](https:\u002F\u002Fdocs.cloud.google.com\u002Fapplication-integration\u002Fdocs\u002Flocations.md.txt) and use `us-central1` as a well-supported default when the user has no preference. The region is the `LOCATION` in every REST path; no `gcloud` default-region setting applies to Application Integration. The target `LOCATION` must be provisioned via the Cloud console or `POST ...\u002Fclients:provision` before authoring integrations (see `application-integration-basics`).\n5. **Authenticate**:\n   - For `curl`: run `gcloud auth login` (changes host or credential state; get the user's approval first) and pass `-H \"Authorization: Bearer $(gcloud auth print-access-token)\"`.\n   - For the Python client (install with `pip install \"google-api-python-client>=2.100.0,\u003C3\" \"google-auth>=2.20.0,\u003C3\"`): run `gcloud auth application-default login` and `gcloud auth application-default set-quota-project PROJECT_ID` (or set `GOOGLE_APPLICATION_CREDENTIALS` for a service account) (changes host or credential state; get the user's approval first).\n   - For service account impersonation: run `gcloud config set auth\u002Fimpersonate_service_account SA_EMAIL` (requires `roles\u002Fiam.serviceAccountTokenCreator`) (changes host or credential state; get the user's approval first).\n6. **Verify IAM roles**:\n   - Create, upload, or edit drafts: `roles\u002Fintegrations.integrationEditor` (covers `versions:upload`, which needs `integrations.integrationVersions.create`, and `versions:publish`)\n   - Publish or unpublish: `roles\u002Fintegrations.integrationDeployer` or `roles\u002Fintegrations.integrationEditor` (`roles\u002Fintegrations.integrationDeployer` alone can publish an existing version but cannot upload via `versions:upload`), plus `roles\u002Fintegrations.serviceAgent` granted to the Application Integration service agent (`service-PROJECT_NUMBER@gcp-sa-integrations.iam.gserviceaccount.com`)\n   - Invoke integrations: `roles\u002Fintegrations.integrationInvoker`\n   - Act as the run-as service account: integrations created on or after October 15, 2026 with any trigger other than an API or Private trigger (for example Schedule, Cloud Scheduler, Cloud Pub\u002FSub, Salesforce, or Connector Event; Private triggers are exempt like API triggers, so a sub-integration whose only trigger is Private can be published without `runAsServiceAccount`) cannot be tested or published unless `runAsServiceAccount` is set (and region governance requires it for all integrations); whenever `runAsServiceAccount` is set, the user publishing or testing the version needs `roles\u002Fiam.serviceAccountUser` on it (and at runtime, the invoking caller or trigger service account needs `roles\u002Fiam.serviceAccountUser` on it).\n\n## Decision Flow\n\n| Goal | Action | Endpoint \u002F Method |\n| :--- | :--- | :--- |\n| Inspect existing integrations in a region | List integrations, then list versions | `GET ...\u002Fintegrations` then `GET ...\u002Fversions` |\n| Export an integration version to source control | Download version JSON | `GET ...\u002Fversions\u002FVERSION_ID:download?fileFormat=JSON` |\n| Create an integration or add a new `DRAFT` version from JSON | Upload version JSON (creates the integration if it does not exist, or adds a new `DRAFT` version; still needs `:publish`) | `POST ...\u002Fintegrations\u002FINTEGRATION_NAME\u002Fversions:upload` |\n| Create a brand-new integration and fail on a name clash | Create first `DRAFT` version with `newIntegration=true` (returns `ALREADY_EXISTS` if `INTEGRATION_NAME` already exists) | `POST ...\u002Fintegrations\u002FINTEGRATION_NAME\u002Fversions?newIntegration=true` |\n| Edit an existing `DRAFT` version | Check `lockHolder`, then patch or upload | `PATCH ...\u002Fversions\u002FVERSION_ID?updateMask=...` |\n| Edit an `ACTIVE` (published) integration | Upload or create a new `DRAFT` version | `POST ...\u002Fintegrations\u002FINTEGRATION_NAME\u002Fversions:upload` |\n| Externalize environment-specific URLs or IDs | Define `CONFIG_\u003CNAME>` and supply at publish | `integrationConfigParameters` + `versions:publish` |\n| Store API keys, OAuth credentials, or certs | Create an authentication profile | `POST ...\u002FauthConfigs` |\n| Test an integration before production | Publish to a non-production project and invoke `:execute` | `POST ...\u002Fversions\u002FVERSION_ID:publish` then `POST ...\u002Fintegrations\u002FINTEGRATION_NAME:execute` |\n| Deploy a `DRAFT` to serve live traffic | Publish version with `configParameters` | `POST ...\u002Fversions\u002FVERSION_ID:publish` |\n| Invoke a published integration synchronously | Call `:execute` with `api_trigger\u002FTRIGGER_NAME` | `POST ...\u002Fintegrations\u002FINTEGRATION_NAME:execute` |\n| Invoke a published integration asynchronously | Call `:schedule` with `scheduleTime` | `POST ...\u002Fintegrations\u002FINTEGRATION_NAME:schedule` |\n| Promote a version across projects (CI\u002FCD) | Download JSON -> Upload to target -> Publish | `:download` -> `:upload` -> `:publish` |\n\nSee [references\u002Frest-api-cheatsheet.md](references\u002Frest-api-cheatsheet.md) for full request parameters, query filters, and IAM permissions for each method.\n\n## Core Authoring Workflow\n\n### 1. List Integrations and Download a Version\n\nList integrations in `LOCATION` and list versions for a specific `INTEGRATION_NAME`:\n\n```bash\nTOKEN=\"$(gcloud auth print-access-token)\"\n\n# List integrations in the region\ncurl -sS -H \"Authorization: Bearer ${TOKEN}\" \\\n  \"https:\u002F\u002Fintegrations.googleapis.com\u002Fv1\u002Fprojects\u002FPROJECT_ID\u002Flocations\u002FLOCATION\u002Fintegrations?pageSize=50\"\n\n# List versions (ordered by most recently modified)\ncurl -sS -H \"Authorization: Bearer ${TOKEN}\" \\\n  \"https:\u002F\u002Fintegrations.googleapis.com\u002Fv1\u002Fprojects\u002FPROJECT_ID\u002Flocations\u002FLOCATION\u002Fintegrations\u002FINTEGRATION_NAME\u002Fversions?orderBy=last_modified_time%20desc\"\n```\n\nDownload a specific version (`VERSION_ID` is the UUID at the end of the version's `name` field) as portable JSON:\n\n```bash\ncurl -sS -H \"Authorization: Bearer ${TOKEN}\" \\\n  \"https:\u002F\u002Fintegrations.googleapis.com\u002Fv1\u002Fprojects\u002FPROJECT_ID\u002Flocations\u002FLOCATION\u002Fintegrations\u002FINTEGRATION_NAME\u002Fversions\u002FVERSION_ID:download?fileFormat=JSON\" \\\n  | jq -r '.content' > integration-version.json\n```\n\n### 2. Understand the `IntegrationVersion` Structure\n\nAn `IntegrationVersion` JSON object contains four primary arrays and version-level settings:\n\n- `triggerConfigs`: Entry points that start the workflow. Each entry requires a unique `triggerNumber`, plus `triggerType` (such as `API` or `PRIVATE_TRIGGER`), optional `triggerId` (auto-generated from `properties` if omitted; for API triggers: `api_trigger\u002FTRIGGER_NAME`), and `startTasks` pointing to the first `taskId`.\n- `taskConfigs`: Units of work. Each entry needs `task` (the task type, such as `EmailTask` in the public upload sample, or `FieldMappingTask` in a discovery enum description), a unique `taskId`, `parameters`, `nextTasks`, and optional `failurePolicy` or `errorCatcherId`.\n- `integrationParameters`: Input, output, and transient variables (`key`, `dataType` such as `STRING_VALUE` or `JSON_VALUE`, `inputOutputType` of `IN`, `OUT`, `IN_OUT`, or `IN_OUT_TYPE_UNSPECIFIED`, and `defaultValue`).\n- `integrationConfigParameters`: Environment-specific config variable definitions (`parameter` with key `` `CONFIG_\u003CNAME>` `` and `parameter.defaultValue`; a top-level `value` in uploaded or created draft JSON is discarded, so per-environment values go in `configParameters` at publish).\n- `runAsServiceAccount`: Dedicated user-managed service account email. When set and no authentication profile (`authConfig`) is configured on the task, it is used to generate the auth token for Connector (`Connectors`), REST (`Call REST Endpoint`), and Cloud Function tasks; an authentication profile configured on the task takes precedence over `runAsServiceAccount`.\n\nAlways use `triggerConfigs`, `taskConfigs`, and `integrationParameters`. Never use `triggerConfigsInternal`, `taskConfigsInternal`, or `integrationParametersInternal` because they are deprecated. See [references\u002Fintegration-version-json.md](references\u002Fintegration-version-json.md) for field schemas, `ValueType` shapes, and a minimal `IntegrationVersion` JSON template.\n\n### 3. Create or Upload a `DRAFT` Version\n\nEach integration allows up to 100 versions and 100 tasks, and `INTEGRATION_NAME` can be at most 64 characters. Calling `versions:upload` creates a new `DRAFT` version—creating the integration automatically if `INTEGRATION_NAME` does not exist yet, or adding the next `DRAFT` version if it already exists (for example, uploading onto version 5 creates version 6). The uploaded version is in `DRAFT` state and still requires `versions:publish` to serve traffic. Use `POST ...\u002Fversions?newIntegration=true` (passing an `IntegrationVersion` JSON body) only when you explicitly want the call to fail with `ALREADY_EXISTS` if `INTEGRATION_NAME` already exists.\n\n```bash\n# Upload integration-version.json as a new DRAFT version\nPAYLOAD=\"$(jq -n --rawfile content integration-version.json '{content: $content, fileFormat: \"JSON\"}')\"\n\ncurl -sS --fail-with-body -X POST \\\n  -H \"Authorization: Bearer $(gcloud auth print-access-token)\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d \"${PAYLOAD}\" \\\n  \"https:\u002F\u002Fintegrations.googleapis.com\u002Fv1\u002Fprojects\u002FPROJECT_ID\u002Flocations\u002FLOCATION\u002Fintegrations\u002FINTEGRATION_NAME\u002Fversions:upload\"\n```\n\nCapture the new `VERSION_ID` from `.integrationVersion.name` in the response (`projects\u002FPROJECT_ID\u002Flocations\u002FLOCATION\u002Fintegrations\u002FINTEGRATION_NAME\u002Fversions\u002FVERSION_ID`).\n\n### 4. Version States and Edit-Lock Rules\n\n| State | Meaning | Editable? | Can Serve Triggers? |\n| :--- | :--- | :--- | :--- |\n| `DRAFT` | Version actively being edited; its edit lock is held by its author (`lockHolder` email) for 15 minutes from the last update | Yes (only by `lockHolder`) | No |\n| `ACTIVE` | Published version serving live traffic | No | Yes |\n| `SNAPSHOT` | Prior draft (when another author takes over in the console) or unpublished version (`PREVIOUSLY_PUBLISHED`) | No | No |\n| `ARCHIVED` | Soft-deleted via `versions.delete` (no undelete; listed as `DELETED` on the Integration versions documentation page) | No | No |\n\n- **Edit-lock enforcement**: A `DRAFT` version's edit lock is held by its author (`lockHolder`). Calling `versions:publish` or `versions.delete` on a `DRAFT` fails if the caller is not the `lockHolder` (`versions:publish` and `versions.delete` release any existing lock when they succeed).\n- **No v1 lock-takeover API**: `takeoverEditLock` does **not** exist on `projects.locations.integrations.versions` in the v1 REST API (it only exists on `versions.testCases`). If a `DRAFT` is locked by another user, wait 15 minutes from `updateTime` for the lock to expire automatically, or click **Take Over** in the Cloud console (which converts the existing draft to `SNAPSHOT` and creates a new `DRAFT`).\n\n### 5. Config Variables vs Authentication Profiles\n\n- **Config variables (`CONFIG_\u003CNAME>`)**: Use for non-secret environment values (backend hostnames, Pub\u002FSub topics, connection names). Define them in `integrationConfigParameters` on the draft (putting any default in `parameter.defaultValue`, because a top-level `value` on a draft is discarded) and pass their per-environment values in `configParameters` when calling `versions:publish`. **Gotcha**: Keys in `configParameters` at publish must **exactly match** a key in `integrationConfigParameters`, backticks included (for example, `` \"`CONFIG_target_url`\" ``); unknown keys are **silently ignored**, not rejected (`versions:publish` returns 200 and the default stays). Config variables cannot configure Integration Connectors entity operations or actions.\n- **Authentication profiles (`authConfigs`)**: Use for API keys, OAuth 2.0 credentials, JWTs, OIDC tokens, SSL\u002FTLS certificates, and service accounts. Auth profiles are regional resources (`projects\u002FPROJECT_ID\u002Flocations\u002FLOCATION\u002FauthConfigs\u002FAUTH_CONFIG_ID`) and are **not** included when you download an integration version. Create them in each target project via `POST ...\u002FauthConfigs` or Terraform `google_integrations_auth_config`.\n\n### 6. Test, Publish, and Unpublish\n\n- **Recommended testing path**: Test integrations by publishing the version in a non-production project (`versions:publish`) and invoking `integrations:execute` there.\n  - **Note on `integrations:test`**: The v1 REST API includes `POST ...\u002Fintegrations\u002FINTEGRATION_NAME:test` for testing a `DRAFT`, which requires `TestIntegrationsRequest.clientId` (\"Required. This is used to identify the client on whose behalf the event will be executed.\"), but the public documentation does not specify what value to pass. Do not invent a `clientId` value; publish to a non-production project and call `:execute` instead. (Draft testing also cannot run `Approval`, `Suspend`, or `Timer` tasks, and Call REST Endpoint authentication profile changes only take effect after publishing.)\n- **Publish (`versions:publish`)**: Deploys the version as `ACTIVE` and releases any edit lock (fails if the version is already `ACTIVE` or `ARCHIVED`, or if it is a `DRAFT` whose `lockHolder` differs from the caller). Any previously `ACTIVE` version is automatically unpublished, and dependent services switch to the newly published version.\n  - **Gotcha**: Publishing a new version automatically cancels all suspended executions of the previous version.\n  - **Gotcha**: Due to eventual consistency, the previous version can continue running for up to 10 minutes after a new version is published.\n- **Unpublish (`versions:unpublish`)**: Moves the `ACTIVE` version to `SNAPSHOT` (tagged `PREVIOUSLY_PUBLISHED`; fails if the version is not `ACTIVE`) and stops triggers and dependent services.\n\n```bash\n# Publish VERSION_ID and supply config variable values (keys must exactly match integrationConfigParameters keys, backticks included; unknown keys are silently ignored)\ncurl -sS --fail-with-body -X POST \\\n  -H \"Authorization: Bearer $(gcloud auth print-access-token)\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"configParameters\": {\"`CONFIG_backend_url`\": \"https:\u002F\u002Fapi.example.com\"}}' \\\n  \"https:\u002F\u002Fintegrations.googleapis.com\u002Fv1\u002Fprojects\u002FPROJECT_ID\u002Flocations\u002FLOCATION\u002Fintegrations\u002FINTEGRATION_NAME\u002Fversions\u002FVERSION_ID:publish\"\n```\n\n### 7. Execute (Sync) vs Schedule (Async)\n\nOnly an `ACTIVE` (published) version can be invoked via `execute` or `schedule`. For API triggers, `triggerId` is always `api_trigger\u002FTRIGGER_NAME` (where `TRIGGER_NAME` is at most 64 characters). Passing `-` as `INTEGRATION_NAME` in `execute` runs all published integrations in that region that share the `triggerId`.\n\n| Method | Mode | Max Timeout | Max API Input | Key Request Fields | Key Response Fields |\n| :--- | :--- | :--- | :--- | :--- | :--- |\n| `integrations:execute` | Sync | 2 minutes | 8 MB | `triggerId`, `inputParameters`, `requestId`, `doNotPropagateError` | `executionId`, `outputParameters`, `parameters` |\n| `integrations:schedule` | Async | 10 minutes | Not documented | `triggerId`, `scheduleTime` (RFC 3339), `inputParameters`, `requestId`, `userGeneratedExecutionId` | `executionInfoIds[]` |\n\n- **Reading `outputParameters` and `executionId`**: In `ExecuteIntegrationsResponse`, `executionId` identifies the run, and `outputParameters` is a JSON object of output variables (system-generated keys such as `` `ExecutionId` `` are wrapped in backticks).\n- **HTTP 200 does not prove every task succeeded**: If `doNotPropagateError` is `true`, or a task uses failure policy `IGNORE` or `NONE` with an alternate path, the HTTP call returns `200 OK` even when a task failed. Always verify the execution record via `executions.list?filter=execution_id=\"EXECUTION_ID\"` (checking `executionDetails.state`, each task's `taskExecutionState`, and `ErrorInfo` in `Execution.responseParameters`; when `doNotPropagateError` is `true`, errors are also returned in event parameters as `PostWithTriggerIdExecutionException`). Do not rely on the legacy\u002Fdeprecated `executionFailed` field.\n\nSee [references\u002Ftriggers-and-execution.md](references\u002Ftriggers-and-execution.md) for all trigger types, `ValueType` input formatting, OpenAPI spec generation, and suspension lifting and approval resolution.\n\n## End-to-End Version and API Workflow Examples\n\nIn downloaded and uploaded `IntegrationVersion` JSON, an `EmailTask` uses four customer-visible parameter keys inside `taskConfigs[].parameters`: `\"To\"` (`stringArray` of recipient addresses), `\"Subject\"` (`stringValue`), `\"BodyFormat\"` (`stringValue`, such as `\"PLAIN_TEXT\"`), and `\"TextBody\"` (`stringValue`). Both runnable examples below also set `\"runAsServiceAccount\": \"RUN_AS_SA\"` (replace `RUN_AS_SA` with your dedicated service account email, such as `order-workflow-sa@PROJECT_ID.iam.gserviceaccount.com`; projects with governance require a run-as service account to publish, and the caller needs `roles\u002Fiam.serviceAccountUser` on it). Executing an `EmailTask` sends a real email, so always confirm the recipient address (`RECIPIENT_EMAIL`) and each state-changing command (upload, publish, and execute) with the user before running.\n\n### Example 1: Upload, Publish, and Invoke Workflow with `curl`\n\nThis example uploads a runnable `IntegrationVersion` JSON (`versions:upload` creates `INTEGRATION_NAME` automatically if it does not exist yet, or adds a new `DRAFT` version if it does), publishes the draft, and invokes `:execute`. The caller must hold `roles\u002Fiam.serviceAccountUser` on `RUN_AS_SA` (projects with governance require a run-as service account to publish). Because `EmailTask` sends real email, confirm `RECIPIENT_EMAIL` and get user confirmation before running the upload, publish, and execute calls.\n\n```bash\nset -euo pipefail\nexport PROJECT_ID=\"my-project\"\nexport LOCATION=\"us-central1\"\nexport INTEGRATION_NAME=\"order-webhook\"\nexport TRIGGER_NAME=\"order_api\"\nTOKEN=\"$(gcloud auth print-access-token)\"\nBASE_URL=\"https:\u002F\u002Fintegrations.googleapis.com\u002Fv1\u002Fprojects\u002F${PROJECT_ID}\u002Flocations\u002F${LOCATION}\"\n\n# 1. Write an IntegrationVersion JSON file (set RUN_AS_SA and confirm RECIPIENT_EMAIL with user first; caller needs roles\u002Fiam.serviceAccountUser on RUN_AS_SA)\ncat \u003C\u003C'EOF' > \u002Ftmp\u002Fversion.json\n{\n  \"description\": \"API-triggered order notification integration\",\n  \"runAsServiceAccount\": \"RUN_AS_SA\",\n  \"triggerConfigs\": [\n    {\n      \"label\": \"API Trigger\",\n      \"triggerType\": \"API\",\n      \"triggerNumber\": \"1\",\n      \"triggerId\": \"api_trigger\u002Forder_api\",\n      \"properties\": {\"Trigger name\": \"order_api\"},\n      \"startTasks\": [{\"taskId\": \"1\"}]\n    }\n  ],\n  \"taskConfigs\": [\n    {\n      \"task\": \"EmailTask\",\n      \"taskId\": \"1\",\n      \"displayName\": \"Send Email\",\n      \"taskExecutionStrategy\": \"WHEN_ALL_SUCCEED\",\n      \"parameters\": {\n        \"To\": {\"key\": \"To\", \"value\": {\"stringArray\": {\"stringValues\": [\"RECIPIENT_EMAIL\"]}}},\n        \"Subject\": {\"key\": \"Subject\", \"value\": {\"stringValue\": \"Order notification for $orderId$\"}},\n        \"BodyFormat\": {\"key\": \"BodyFormat\", \"value\": {\"stringValue\": \"PLAIN_TEXT\"}},\n        \"TextBody\": {\"key\": \"TextBody\", \"value\": {\"stringValue\": \"Processing order $orderId$.\"}}\n      }\n    }\n  ],\n  \"integrationParameters\": [\n    {\n      \"key\": \"orderId\",\n      \"dataType\": \"STRING_VALUE\",\n      \"inputOutputType\": \"IN_OUT\",\n      \"defaultValue\": {\"stringValue\": \"ORD-0000\"}\n    }\n  ]\n}\nEOF\n\n# 2. Upload as a new DRAFT version — creates INTEGRATION_NAME if it does not exist yet (confirm with user first)\nUPLOAD_BODY=\"$(jq -n --rawfile content \u002Ftmp\u002Fversion.json '{content: $content, fileFormat: \"JSON\"}')\"\nVERSION_NAME=\"$(curl -sS --fail-with-body -X POST \\\n  -H \"Authorization: Bearer ${TOKEN}\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d \"${UPLOAD_BODY}\" \\\n  \"${BASE_URL}\u002Fintegrations\u002F${INTEGRATION_NAME}\u002Fversions:upload\" \\\n  | jq -r '.integrationVersion.name')\"\nVERSION_ID=\"${VERSION_NAME##*\u002F}\"\n[[ -n \"${VERSION_ID}\" && \"${VERSION_ID}\" != \"null\" ]] || { echo \"upload failed: ${VERSION_NAME}\" >&2; exit 1; }\n\n# 3. Publish the DRAFT version (confirm with user first)\ncurl -sS --fail-with-body -X POST \\\n  -H \"Authorization: Bearer ${TOKEN}\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{}' \\\n  \"${BASE_URL}\u002Fintegrations\u002F${INTEGRATION_NAME}\u002Fversions\u002F${VERSION_ID}:publish\"\n\n# 4. Invoke the published version synchronously — sends a real email to RECIPIENT_EMAIL (confirm with user first)\ncurl -sS --fail-with-body -X POST \\\n  -H \"Authorization: Bearer ${TOKEN}\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -H \"X-Server-Timeout: 120\" \\\n  -d \"{\n    \\\"triggerId\\\": \\\"api_trigger\u002F${TRIGGER_NAME}\\\",\n    \\\"inputParameters\\\": {\n      \\\"orderId\\\": {\\\"stringValue\\\": \\\"ORD-1001\\\"}\n    }\n  }\" \\\n  \"${BASE_URL}\u002Fintegrations\u002F${INTEGRATION_NAME}:execute\" \\\n  | jq '{executionId, outputParameters}'\n```\n\n### Example 2: Python Discovery Client Workflow (Built from the API Reference)\n\nNo official Python sample is published on `docs.cloud.google.com`; the following workflow is built from the v1 API discovery document (`google-api-python-client`). The caller must hold `roles\u002Fiam.serviceAccountUser` on `RUN_AS_SA` (projects with governance require a run-as service account to publish). Because `EmailTask` sends real email, confirm `RECIPIENT_EMAIL` and each state-changing call (`upload`, `publish`, `execute`) with the user before running. Install dependencies first with `pip install \"google-api-python-client>=2.100.0,\u003C3\" \"google-auth>=2.20.0,\u003C3\"` (changes host or credential state; get the user's approval first).\n\n```python\nimport json\nfrom googleapiclient.discovery import build\n\nPROJECT_ID = \"PROJECT_ID\"\nLOCATION = \"LOCATION\"\nINTEGRATION_NAME = \"INTEGRATION_NAME\"\nTRIGGER_NAME = \"TRIGGER_NAME\"\nRECIPIENT_EMAIL = \"RECIPIENT_EMAIL\"\nparent = f\"projects\u002F{PROJECT_ID}\u002Flocations\u002F{LOCATION}\"\nintegration_path = f\"{parent}\u002Fintegrations\u002F{INTEGRATION_NAME}\"\n\nservice = build(\"integrations\", \"v1\")\nintegrations_api = service.projects().locations().integrations()\nversions_api = integrations_api.versions()\n\n# 1. List existing integrations (read-only)\nlisted = integrations_api.list(parent=parent, pageSize=20).execute()\nfor item in listed.get(\"integrations\", []):\n    print(item[\"name\"], \"active=\", item.get(\"active\"))\n\n# 2. Upload a DRAFT version — creates INTEGRATION_NAME if it does not exist yet (set RUN_AS_SA and confirm RECIPIENT_EMAIL and action with user before running; caller needs roles\u002Fiam.serviceAccountUser on RUN_AS_SA)\nversion_dict = {\n    \"description\": \"API-triggered order notification integration\",\n    \"runAsServiceAccount\": \"RUN_AS_SA\",\n    \"triggerConfigs\": [{\n        \"label\": \"API Trigger\",\n        \"triggerType\": \"API\",\n        \"triggerNumber\": \"1\",\n        \"triggerId\": f\"api_trigger\u002F{TRIGGER_NAME}\",\n        \"properties\": {\"Trigger name\": TRIGGER_NAME},\n        \"startTasks\": [{\"taskId\": \"1\"}],\n    }],\n    \"taskConfigs\": [{\n        \"task\": \"EmailTask\",\n        \"taskId\": \"1\",\n        \"displayName\": \"Send Email\",\n        \"taskExecutionStrategy\": \"WHEN_ALL_SUCCEED\",\n        \"parameters\": {\n            \"To\": {\"key\": \"To\", \"value\": {\"stringArray\": {\"stringValues\": [RECIPIENT_EMAIL]}}},\n            \"Subject\": {\"key\": \"Subject\", \"value\": {\"stringValue\": \"Order notification for $orderId$\"}},\n            \"BodyFormat\": {\"key\": \"BodyFormat\", \"value\": {\"stringValue\": \"PLAIN_TEXT\"}},\n            \"TextBody\": {\"key\": \"TextBody\", \"value\": {\"stringValue\": \"Processing order $orderId$.\"}},\n        },\n    }],\n    \"integrationParameters\": [{\n        \"key\": \"orderId\",\n        \"dataType\": \"STRING_VALUE\",\n        \"inputOutputType\": \"IN_OUT\",\n        \"defaultValue\": {\"stringValue\": \"ORD-0000\"},\n    }],\n}\nupload_resp = versions_api.upload(\n    parent=integration_path,\n    body={\"content\": json.dumps(version_dict), \"fileFormat\": \"JSON\"},\n).execute()\nversion_name = upload_resp[\"integrationVersion\"][\"name\"]\n\n# 3. Publish the DRAFT version (confirm with user before running)\nversions_api.publish(\n    name=version_name,\n    body={\"configParameters\": {}},\n).execute()\n\n# 4. Invoke the published version synchronously — sends a real email to RECIPIENT_EMAIL (confirm with user before running)\nexec_resp = integrations_api.execute(\n    name=integration_path,\n    body={\n        \"triggerId\": f\"api_trigger\u002F{TRIGGER_NAME}\",\n        \"inputParameters\": {\"orderId\": {\"stringValue\": \"ORD-1001\"}},\n    },\n).execute()\nprint(\"executionId:\", exec_resp.get(\"executionId\"))\nprint(\"outputParameters:\", exec_resp.get(\"outputParameters\"))\n```\n\n## Common Task Gotchas and Limits\n\n- **Data limits**: Total cumulative integration data (including inputs and outputs) is capped at 30 MB. Above 10 MB, local execution logging is automatically disabled.\n- **Data Mapping**: Input and output types must match (cast with `TO_STRING`, `TO_INT`, and so on). Always set a fallback value or null inputs fail the task.\n- **Call REST Endpoint**: Reaches the public internet only (cannot reach VPC-internal hosts). Fails on HTTP 4xx and 5xx responses. Request max 20 MB, response max 34 MB, max 100 concurrent calls.\n- **JavaScript**: Uses the V8 engine (Rhino was deprecated January 24, 2025). Never remove the `executesScript(event)` entrypoint. Max script request size is 25 MB.\n- **Connectors task**: Requires an active Integration Connectors connection (billed separately). Timeout is 3 minutes; Application Integration quotas cap data to and from connections at 8 MB (Integration Connectors quotas list a 10 MB Connectors task payload limit; response max 20 MB).\n- **Approval vs Suspend**: Approval tasks can be approved or rejected via `POST ...\u002Fexecutions\u002FEXECUTION_ID\u002Fsuspensions\u002FSUSPENSION_ID:resolve` with body `{\"suspension\": {\"eventExecutionInfoId\": \"EXECUTION_ID\", \"integration\": \"INTEGRATION_NAME\", \"taskId\": \"TASK_ID\", \"state\": \"LIFTED\"}}` (or `\"REJECTED\"`), which is the same call the Cloud console Approve\u002FReject buttons make and requires `integrations.suspensions.resolve`. Suspend tasks are resumed via `POST ...\u002Fintegrations\u002FINTEGRATION_NAME\u002Fexecutions\u002FEXECUTION_ID\u002Fsuspensions\u002FSUSPENSION_ID:lift` (where `-` can replace `INTEGRATION_NAME` and either `EXECUTION_ID` or `SUSPENSION_ID`, provided at least one of `EXECUTION_ID` or `SUSPENSION_ID` is concrete; accepts an optional `suspensionResult` defaulting to `Resolved`), which requires `integrations.suspensions.lift`. Listing suspensions (`suspensions.list`) requires `integrations.suspensions.list`. All three permissions are included in `roles\u002Fintegrations.suspensionResolver` (and `roles\u002Fintegrations.integrationAdmin`); resuming also requires `roles\u002Fiam.serviceAccountUser` on the integration's run-as service account, requires user confirmation first, and runs the remainder of the execution in async mode.\n- **Loops and Sub-integrations**: For Each Loop and While Loop allow up to 8,000 iterations; For Each Parallel allows up to 10,000 iterations (default parallelism 50). For sub-integration calls (`Call Integration`, `For Each Loop`, `For Each Parallel`), child responses\u002Foutput parameters are returned to the parent only in `SYNC` mode (`ASYNC` calls return no output parameters); avoid `SYNC` mode if the sub-integration exceeds 2 minutes or includes an Approval task.\n\nSee [references\u002Ftasks-and-gotchas.md](references\u002Ftasks-and-gotchas.md) for the full task catalog, limits, variable reference syntax, and retry strategies.\n\n## Promoting Between Environments (CI\u002FCD)\n\nTo promote an integration from development to staging or production:\n\n1. Externalize environment-specific values (endpoints, project IDs, connection names) as `CONFIG_\u003CNAME>` config variables.\n2. Provision the target region, run-as service account, Integration Connectors connections, and authentication profiles (`authConfigs`) in the target project first (these infrastructure resources are not created by version uploads).\n3. Download the tested version JSON (`versions:download?fileFormat=JSON`), commit it to Git alongside per-environment config files, rewrite `.runAsServiceAccount` to the target environment's run-as service account (`jq --arg sa \"$RUN_AS_SA\" '.runAsServiceAccount = $sa' integration.json > integration.env.json`) before uploading, and run `versions:upload` + `versions:publish` from Cloud Build or your CI runner (`versions:upload` creates the integration automatically in the target project if it does not exist yet, producing a `DRAFT` that `:publish` activates).\n\nSee [references\u002Fcicd-promotion.md](references\u002Fcicd-promotion.md) for the complete promotion checklist and a ready-to-use Cloud Build pipeline.\n\n## Cleanup and Cost Management\n\n- **Pricing**: An active billing account must be attached to the project even when usage stays within the monthly free tier. Application Integration offers a monthly free tier for executions, data processed, and Google service connector nodes, after which executions, data processed, and Integration Connectors nodes incur charges. Review current thresholds and rates on the [Application Integration pricing page](https:\u002F\u002Fcloud.google.com\u002Fapplication-integration\u002Fpricing) and estimate costs with the Google Cloud Pricing Calculator.\n- **Unpublish to stop triggers**: Run `POST ...\u002Fversions\u002FVERSION_ID:unpublish` (after user confirmation) to stop scheduled, Pub\u002FSub, or API triggers from firing.\n- **Delete versions or integrations**: `DELETE ...\u002Fversions\u002FVERSION_ID` archives a single version (deleting all versions deletes the integration). `DELETE ...\u002Fintegrations\u002FINTEGRATION_NAME` permanently deletes the integration and all its versions.\n- **Connector nodes**: Unpublishing or deleting an integration does not delete Integration Connectors connections. Delete unused connections at `https:\u002F\u002Fconsole.cloud.google.com\u002Fconnectors\u002Fconnections` to stop hourly node charges.\n\n## Reference Guides\n\n- [Usage Attribution](references\u002Fattribution.md): Usage attribution for `gcloud` CLI commands, direct `curl` REST calls, and Python discovery-client snippets run in the terminal.\n- [REST API Cheatsheet](references\u002Frest-api-cheatsheet.md): v1 and v2 endpoints, parameters, filters, and IAM permissions.\n- [IntegrationVersion JSON Reference](references\u002Fintegration-version-json.md): Schema for `triggerConfigs`, `taskConfigs`, `integrationParameters`, `ValueType`, and config variables.\n- [Triggers and Execution Reference](references\u002Ftriggers-and-execution.md): Trigger types, `triggerId` formats, `:execute` vs `:schedule`, OpenAPI generation, and lifting or resolving suspensions.\n- [Tasks and Gotchas Reference](references\u002Ftasks-and-gotchas.md): Task-by-task rules, quotas, variable reference syntax, and error handling strategies.\n- [CI\u002FCD Promotion Reference](references\u002Fcicd-promotion.md): Cross-project promotion checklist, environment config pattern, and Cloud Build workflow.\n",{"name":5,"metadata":52,"description":7},{"version":53,"category":54},"1.0.2","ApplicationDevelopment","main","bb9b3f464aae37af360d079c1d863524955b0363","skills\u002Fcloud\u002Fapplication-integration-authoring",1994,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills","https:\u002F\u002Fgithub.com\u002Fgoogle","https:\u002F\u002Fopensource.google\u002F","Google ❤️ Open Source","2026-11-12 00:37:41",{"source":8,"skillName":5,"commands":65,"prompt":75,"zipAvailable":76},[66,69,72],{"tool":67,"command":68},"npx","npx skills add google\u002Fskills --skill application-integration-authoring",{"tool":70,"command":71},"bunx","bunx skills add google\u002Fskills --skill application-integration-authoring",{"tool":73,"command":74},"pnpm","pnpm dlx skills add google\u002Fskills --skill application-integration-authoring","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapplication-integration-authoring\u002FSKILL.md",true,[78,84,88,92,96,100],{"path":79,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":82,"sourceHtmlUrl":83},"references\u002Fattribution.md","REFERENCE","参考文档",5091,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapplication-integration-authoring\u002Freferences\u002Fattribution.md",{"path":85,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":86,"sourceHtmlUrl":87},"references\u002Fcicd-promotion.md",11646,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapplication-integration-authoring\u002Freferences\u002Fcicd-promotion.md",{"path":89,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":90,"sourceHtmlUrl":91},"references\u002Fintegration-version-json.md",14704,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapplication-integration-authoring\u002Freferences\u002Fintegration-version-json.md",{"path":93,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":94,"sourceHtmlUrl":95},"references\u002Frest-api-cheatsheet.md",19800,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapplication-integration-authoring\u002Freferences\u002Frest-api-cheatsheet.md",{"path":97,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":98,"sourceHtmlUrl":99},"references\u002Ftasks-and-gotchas.md",13302,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapplication-integration-authoring\u002Freferences\u002Ftasks-and-gotchas.md",{"path":101,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":102,"sourceHtmlUrl":103},"references\u002Ftriggers-and-execution.md",15764,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapplication-integration-authoring\u002Freferences\u002Ftriggers-and-execution.md",[105,136,167,194,221,245],{"skillNo":106,"name":107,"nameZh":6,"description":108,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":109,"categories":110,"keywords":115,"publishedAt":47},"01M6998S2EZKD3GEJ7F943RZTE","sign-in-with-google-web","Implement, configure, and secure Sign In With Google (SiwG) using Google Identity Services (GIS \u002F `https:\u002F\u002Faccounts.google.com\u002Fgsi\u002Fclient`) across web architectures. Use when creating Google sign-in buttons, implementing Google One Tap with FedCM, integrating GIS in React\u002FNext.js\u002FAngular\u002FHTML, verifying ID tokens on backend runtimes (Python\u002FNode.js\u002FGo\u002FJava), enforcing Google Workspace domain restrictions (`hd`), securing client-side ID tokens via WebCrypto nonces or encrypted IndexedDB, embedding in cross-origin iframes via the Intermediate Iframe API (`https:\u002F\u002Faccounts.google.com\u002Fgsi\u002Fintermediate`), configuring Content Security Policy (CSP), COOP, and Permissions Policy (`identity-credentials-get`) headers, or handling sign-out and revocation. Don't use for legacy Google Sign-In (`gapi.auth2`), Google Cloud IAM service accounts, or Android\u002FiOS native Credential Manager.","https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fidentity\u002Fsign-in-with-google-web\u002FSKILL.md",[111,114],{"categoryNo":112,"categoryName":113,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000009","前端开发",{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[9,116,117,118,119,120,121,122,123,45,124,125,46,126,127,40,128,129,130,131,132,133,134,135],"sign in with google web","sign","with","web","android","go","security","google workspace","implement","policy","implementing","react","permissions","buttons","secure","manager","identity","architectures","domain","runtimes",{"skillNo":137,"name":138,"nameZh":6,"description":139,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":140,"categories":141,"keywords":151,"publishedAt":47},"01M6998PHPDCAD684VW5WRG83D","dpop-adoption","Implement and debug OAuth 2.0 DPoP (RFC 9449) refresh token sender-constraining for WebCrypto, Node.js ES6, and browser runtimes integrating with Google's OAuth platform. Use when configuring non-extractable asymmetric key pairs (P-256), generating DPoP Proof JWTs for authorization code exchange and token refresh, or handling 400 use_dpop_nonce challenge retry loops at oauth2.googleapis.com\u002Ftoken. Don't use for unconstrained OAuth 2.0 flows (where refresh tokens are not bound to a client key pair), or for Google Cloud IAM \u002F service account authentication.","https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fidentity\u002Fdpop-adoption\u002FSKILL.md",[142,143,146],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":144,"categoryName":145,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",{"categoryNo":147,"categoryName":148,"taxonomyType":149,"taxonomyTypeDesc":150},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[152,153,9,45,154,155,156,157,158,159,40,160,161,162,163,164,165,132,135,166,33],"dpop adoption","adoption","es6","retry","authentication","flows","challenge","code","browser","loops","at","node","token","pair","platform",{"skillNo":168,"name":169,"nameZh":6,"description":170,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":171,"categories":172,"keywords":179,"publishedAt":47},"01M6998M72TC73EEAQH4XC7EXW","retrieving-developer-knowledge","Searches, retrieves, and synthesizes official Google developer documentation across Google Cloud, AI\u002FGemini, Android, Chrome, Web, Flutter, Go, Firebase, and other Google developer platforms. Integrates with the Developer Knowledge MCP server (search_documents, get_documents, answer_query) or the Developer Knowledge REST API fallback. Use when searching for gcloud CLI commands, API syntax, IAM permissions, official documentation, architectural comparisons, or product choice overviews. Don't use for local filesystem lookups or non-Google documentation.","https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fdevelopers\u002Fretrieving-developer-knowledge\u002FSKILL.md",[173,174,177,178],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":175,"categoryName":176,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000021","资料检索与研究",{"categoryNo":144,"categoryName":145,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":147,"categoryName":148,"taxonomyType":149,"taxonomyTypeDesc":150},[180,181,182,183,45,184,185,186,187,188,189,190,191,33,192,31,39,40,193,128],"retrieving developer knowledge","knowledge","developer","MCP","mcp server","official","developer documentation","get","search","local","choice","documents","query","documentation",{"skillNo":195,"name":196,"nameZh":6,"description":197,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":198,"categories":199,"keywords":200,"publishedAt":47},"01M6998J2CZEXS9BES9S3WWFKC","migrate-to-rust","Clones, analyzes, plans, and migrates legacy codebases (C++, Fortran, Java, Python, C, Shell) from a GitHub URL or local directory to a modern, high-performance, deterministic, 100% Safe Rust architecture, tracking live telemetry across every task bucket, test failure\u002Ffix cycle, and Profile-Guided Optimization (PGO) stage, and producing a first-person AI storytelling report and interactive HTML infographic. Use when migrating a GitHub repository or local codebase to Rust, porting legacy C++, Fortran, Python, or Java workloads to memory-safe Rust, designing Struct-of-Arrays (SoA) or SIMD Rust architectures, or optimizing Rust ports with LLVM PGO. Don't use for unrelated non-Rust refactoring or single-line syntax questions.","https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fdevelopers\u002Fmigrate-to-rust\u002FSKILL.md",[],[201,202,203,204,205,206,207,208,46,209,210,211,212,213,214,189,215,216,217,33,218,219,39,220],"migrate to rust","rust","migrate","github","memory","performance","directory","safe","ports","llvm","soa","cycle","porting","fix","arrays","infographic","high-performance","URL","architecture","unrelated",{"skillNo":222,"name":223,"nameZh":6,"description":224,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":225,"categories":226,"keywords":230,"publishedAt":47},"01M6998GCC3K29WZ82KXMB9E02","finding-google-skills","Google platform decision and setup guidance, loaded on demand from Google's skill catalog. Use when a developer is choosing or setting up part of their stack, such as where to run a service, a database, storage, messaging, authentication, analytics, ads, or AI model serving, and a Google product is a reasonable candidate - whether or not a vendor is named - or when a request names a Google product or API. Brings in the matching Google skill so the answer can weigh Google options, their trade-offs, and when they are not the right fit. Skip when the stack is already settled on another provider and no Google product is named, or the task involves no platform choice.","https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fdevelopers\u002Ffinding-google-skills\u002FSKILL.md",[227,228,229],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":144,"categoryName":145,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":147,"categoryName":148,"taxonomyType":149,"taxonomyTypeDesc":150},[9,231,232,38,233,166,234,235,236,237,238,156,239,240,241,190,242,243,33,244,31],"finding google skills","storage","messaging","model","trade-offs","provider","answer","fit","catalog","up","decision","use","database","candidate",{"skillNo":246,"name":247,"nameZh":6,"description":248,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":249,"categories":250,"keywords":253,"publishedAt":47},"01M6998DWNHTH0BS6W1CY8ZXMP","workload-manager-basics","Use this skill to manage Google Cloud Workload Manager evaluations, rules, scanned resources, and validation results by using public client libraries and the REST API. Use when you need to inspect workload best-practice rules, create and run evaluations for Google Cloud general best practices, SAP, SQL Server, or custom organizational rules, review violations, export results to BigQuery, or automate Workload Manager through client libraries because no service-specific public CLI or MCP server is available. Don't use for general Google Compute Engine instance management, VPC configuration, or standard IAM auditing.","https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fworkload-manager-basics\u002FSKILL.md",[251,252],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":144,"categoryName":145,"taxonomyType":21,"taxonomyTypeDesc":22},[254,255,131,256,9,183,257,45,184,258,259,260,261,33,262,31,40,263,264,265,266],"workload manager basics","workload","basics","cli","review","vpc","resources","inspect","you","create","libraries","server","run"]